pict-section-flow 0.0.3 → 0.0.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pict-section-flow",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Pict Section Flow Diagram",
5
5
  "main": "source/Pict-Section-Flow.js",
6
6
  "scripts": {
@@ -11,12 +11,14 @@
11
11
  "author": "steven velozo <steven@velozo.com>",
12
12
  "license": "MIT",
13
13
  "dependencies": {
14
- "pict-view": "^1.0.67",
14
+ "fable-serviceproviderbase": "^3.0.19",
15
15
  "pict-provider": "^1.0.12",
16
- "fable-serviceproviderbase": "^3.0.19"
16
+ "pict-section-form": "^1.0.194",
17
+ "pict-view": "^1.0.67"
17
18
  },
18
19
  "devDependencies": {
19
- "pict": "^1.0.354",
20
- "quackage": "^1.0.59"
20
+ "pict": "^1.0.357",
21
+ "pict-router": "^1.0.6",
22
+ "quackage": "^1.0.64"
21
23
  }
22
24
  }
@@ -105,6 +105,7 @@ class PictFlowCard extends libFableServiceProviderBase
105
105
  this.cardPortLabelsOnHover = (typeof tmpOptions.PortLabelsOnHover === 'boolean') ? tmpOptions.PortLabelsOnHover : false;
106
106
  this.cardPortLabelsVertical = (typeof tmpOptions.PortLabelsVertical === 'boolean') ? tmpOptions.PortLabelsVertical : false;
107
107
  this.cardPortLabelPadding = (typeof tmpOptions.PortLabelPadding === 'boolean') ? tmpOptions.PortLabelPadding : false;
108
+ this.cardPortLabelsOutside = (typeof tmpOptions.PortLabelsOutside === 'boolean') ? tmpOptions.PortLabelsOutside : false;
108
109
  this.cardLabelsInFront = (typeof tmpOptions.LabelsInFront === 'boolean') ? tmpOptions.LabelsInFront : true;
109
110
  }
110
111
 
@@ -184,6 +185,7 @@ class PictFlowCard extends libFableServiceProviderBase
184
185
  tmpResult.PortLabelsOnHover = this.cardPortLabelsOnHover;
185
186
  tmpResult.PortLabelsVertical = this.cardPortLabelsVertical;
186
187
  tmpResult.PortLabelPadding = this.cardPortLabelPadding;
188
+ tmpResult.PortLabelsOutside = this.cardPortLabelsOutside;
187
189
  tmpResult.LabelsInFront = this.cardLabelsInFront;
188
190
 
189
191
  // Include properties panel config if defined
@@ -251,5 +253,6 @@ module.exports.default_configuration =
251
253
  PortLabelsOnHover: false,
252
254
  PortLabelsVertical: false,
253
255
  PortLabelPadding: false,
256
+ PortLabelsOutside: false,
254
257
  LabelsInFront: true
255
258
  };
@@ -67,7 +67,7 @@ class FlowCardPropertiesPanelTemplate extends libPictFlowCardPropertiesPanel
67
67
  if (!tmpTemplateHash) return;
68
68
 
69
69
  let tmpRecord = this._NodeData;
70
- let tmpHTML = this.fable.parseTemplate(tmpTemplateHash, tmpRecord, null, [tmpRecord]);
70
+ let tmpHTML = this.fable.parseTemplateByHash(tmpTemplateHash, tmpRecord, null, [tmpRecord]);
71
71
  this._ContentContainer.innerHTML = tmpHTML;
72
72
  }
73
73
  }
@@ -6,6 +6,16 @@ const libFableServiceProviderBase = require('fable-serviceproviderbase');
6
6
  * Shared geometry utilities for the flow diagram.
7
7
  * Provides direction vectors and edge center calculations used by
8
8
  * connections, tethers, and other flow components.
9
+ *
10
+ * Port Side values (12 positions):
11
+ *
12
+ * Top edge: 'top-left' 'top' 'top-right'
13
+ * Left edge: 'left-top' 'left' 'left-bottom'
14
+ * Right edge: 'right-top' 'right' 'right-bottom'
15
+ * Bottom edge: 'bottom-left' 'bottom' 'bottom-right'
16
+ *
17
+ * The old 4-value sides ('left', 'right', 'top', 'bottom') map to
18
+ * the middle position on each edge for backward compatibility.
9
19
  */
10
20
  class PictProviderFlowGeometry extends libFableServiceProviderBase
11
21
  {
@@ -16,15 +26,55 @@ class PictProviderFlowGeometry extends libFableServiceProviderBase
16
26
  this.serviceType = 'PictProviderFlowGeometry';
17
27
  }
18
28
 
29
+ /**
30
+ * Extract the edge name from a Side value.
31
+ *
32
+ * Maps all 12 positions (and the 4 legacy values) back to
33
+ * the edge they sit on: 'left', 'right', 'top', or 'bottom'.
34
+ *
35
+ * @param {string} pSide - Any valid Side value
36
+ * @returns {string} The edge: 'left', 'right', 'top', or 'bottom'
37
+ */
38
+ getEdgeFromSide(pSide)
39
+ {
40
+ switch (pSide)
41
+ {
42
+ case 'left-top':
43
+ case 'left':
44
+ case 'left-bottom':
45
+ return 'left';
46
+
47
+ case 'right-top':
48
+ case 'right':
49
+ case 'right-bottom':
50
+ return 'right';
51
+
52
+ case 'top-left':
53
+ case 'top':
54
+ case 'top-right':
55
+ return 'top';
56
+
57
+ case 'bottom-left':
58
+ case 'bottom':
59
+ case 'bottom-right':
60
+ return 'bottom';
61
+
62
+ default:
63
+ return 'right';
64
+ }
65
+ }
66
+
19
67
  /**
20
68
  * Get the outward unit direction vector for a given side.
21
69
  *
22
- * @param {string} pSide - 'left', 'right', 'top', or 'bottom'
70
+ * All positions on the same edge share the same direction vector.
71
+ *
72
+ * @param {string} pSide - Any valid Side value (12 positions or 4 legacy)
23
73
  * @returns {{dx: number, dy: number}}
24
74
  */
25
75
  sideDirection(pSide)
26
76
  {
27
- switch (pSide)
77
+ switch (this.getEdgeFromSide(pSide))
28
78
  {
29
79
  case 'left': return { dx: -1, dy: 0 };
30
80
  case 'right': return { dx: 1, dy: 0 };
@@ -63,12 +113,17 @@ class PictProviderFlowGeometry extends libFableServiceProviderBase
63
113
  /**
64
114
  * Calculate a port's local position relative to node origin.
65
115
  *
66
- * For left and right side ports, positioning is offset below the title bar
67
- * so that ports never overlap the header area.
116
+ * Supports 12 positions (3 zones per edge). For left/right edges,
117
+ * the body area below the title bar is divided into three vertical zones
118
+ * (start/middle/end). For top/bottom edges, the full width is divided
119
+ * into three horizontal zones.
68
120
  *
69
- * @param {string} pSide - 'left', 'right', 'top', 'bottom'
70
- * @param {number} pIndex - Index of this port on its side
71
- * @param {number} pTotal - Total ports on this side
121
+ * Multiple ports sharing the same Side value distribute evenly within
122
+ * their zone.
123
+ *
124
+ * @param {string} pSide - Side value (any of 12 positions or 4 legacy)
125
+ * @param {number} pIndex - Index of this port within its Side group
126
+ * @param {number} pTotal - Total ports with this Side value
72
127
  * @param {number} pWidth - Node width
73
128
  * @param {number} pHeight - Node height
74
129
  * @param {number} pTitleBarHeight - Height of the node title bar
@@ -76,30 +131,66 @@ class PictProviderFlowGeometry extends libFableServiceProviderBase
76
131
  */
77
132
  getPortLocalPosition(pSide, pIndex, pTotal, pWidth, pHeight, pTitleBarHeight)
78
133
  {
79
- let tmpSpacing;
134
+ let tmpEdge = this.getEdgeFromSide(pSide);
135
+ let tmpZone = this._getZoneFromSide(pSide);
136
+
137
+ if (tmpEdge === 'left' || tmpEdge === 'right')
138
+ {
139
+ let tmpX = (tmpEdge === 'left') ? 0 : pWidth;
140
+ let tmpBodyHeight = pHeight - pTitleBarHeight;
141
+ let tmpZoneStart = pTitleBarHeight + tmpBodyHeight * tmpZone.start;
142
+ let tmpZoneHeight = tmpBodyHeight * (tmpZone.end - tmpZone.start);
143
+ let tmpSpacing = tmpZoneHeight / (pTotal + 1);
144
+ let tmpY = tmpZoneStart + tmpSpacing * (pIndex + 1);
145
+ return { x: tmpX, y: tmpY };
146
+ }
80
147
 
148
+ // top or bottom
149
+ let tmpY = (tmpEdge === 'top') ? 0 : pHeight;
150
+ let tmpZoneStart = pWidth * tmpZone.start;
151
+ let tmpZoneWidth = pWidth * (tmpZone.end - tmpZone.start);
152
+ let tmpSpacing = tmpZoneWidth / (pTotal + 1);
153
+ let tmpX = tmpZoneStart + tmpSpacing * (pIndex + 1);
154
+ return { x: tmpX, y: tmpY };
155
+ }
156
+
157
+ /**
158
+ * Get the zone fraction (start, end) for a Side value.
159
+ *
160
+ * Each edge is divided into three zones of equal size:
161
+ * start: 0.0 — 0.333
162
+ * middle: 0.333 — 0.667
163
+ * end: 0.667 — 1.0
164
+ *
165
+ * @param {string} pSide
166
+ * @returns {{start: number, end: number}}
167
+ */
168
+ _getZoneFromSide(pSide)
169
+ {
81
170
  switch (pSide)
82
171
  {
83
- case 'left':
84
- {
85
- let tmpBodyHeight = pHeight - pTitleBarHeight;
86
- tmpSpacing = tmpBodyHeight / (pTotal + 1);
87
- return { x: 0, y: pTitleBarHeight + tmpSpacing * (pIndex + 1) };
88
- }
89
- case 'right':
90
- {
91
- let tmpBodyHeight = pHeight - pTitleBarHeight;
92
- tmpSpacing = tmpBodyHeight / (pTotal + 1);
93
- return { x: pWidth, y: pTitleBarHeight + tmpSpacing * (pIndex + 1) };
94
- }
95
- case 'top':
96
- tmpSpacing = pWidth / (pTotal + 1);
97
- return { x: tmpSpacing * (pIndex + 1), y: 0 };
98
- case 'bottom':
99
- tmpSpacing = pWidth / (pTotal + 1);
100
- return { x: tmpSpacing * (pIndex + 1), y: pHeight };
101
- default:
102
- return { x: pWidth, y: pHeight / 2 };
172
+ // Left edge: top, middle, bottom
173
+ case 'left-top': return { start: 0.0, end: 0.333 };
174
+ case 'left': return { start: 0.333, end: 0.667 };
175
+ case 'left-bottom': return { start: 0.667, end: 1.0 };
176
+
177
+ // Right edge: top, middle, bottom
178
+ case 'right-top': return { start: 0.0, end: 0.333 };
179
+ case 'right': return { start: 0.333, end: 0.667 };
180
+ case 'right-bottom': return { start: 0.667, end: 1.0 };
181
+
182
+ // Top edge: left, middle, right
183
+ case 'top-left': return { start: 0.0, end: 0.333 };
184
+ case 'top': return { start: 0.333, end: 0.667 };
185
+ case 'top-right': return { start: 0.667, end: 1.0 };
186
+
187
+ // Bottom edge: left, middle, right
188
+ case 'bottom-left': return { start: 0.0, end: 0.333 };
189
+ case 'bottom': return { start: 0.333, end: 0.667 };
190
+ case 'bottom-right': return { start: 0.667, end: 1.0 };
191
+
192
+ // Fallback: full range (legacy behavior)
193
+ default: return { start: 0.0, end: 1.0 };
103
194
  }
104
195
  }
105
196
  }
@@ -292,22 +292,28 @@ class PictViewFlowNode extends libPictView
292
292
 
293
293
  let tmpPortLabelsVertical = (pNodeTypeConfig && pNodeTypeConfig.PortLabelsVertical);
294
294
  let tmpPortLabelPadding = (pNodeTypeConfig && pNodeTypeConfig.PortLabelPadding);
295
+ let tmpPortLabelsOutside = (pNodeTypeConfig && pNodeTypeConfig.PortLabelsOutside);
296
+ let tmpGeometryProvider = this._FlowView._GeometryProvider;
295
297
 
296
- // Group ports by side and direction for positioning
297
- let tmpPortsBySide = { left: [], right: [], top: [], bottom: [] };
298
+ // Group ports by their Side value (supports all 12 positions)
299
+ let tmpPortsBySide = {};
298
300
  for (let i = 0; i < pNodeData.Ports.length; i++)
299
301
  {
300
302
  let tmpPort = pNodeData.Ports[i];
301
303
  let tmpSide = tmpPort.Side || (tmpPort.Direction === 'input' ? 'left' : 'right');
302
- if (tmpPortsBySide[tmpSide])
304
+ if (!tmpPortsBySide[tmpSide])
303
305
  {
304
- tmpPortsBySide[tmpSide].push(tmpPort);
306
+ tmpPortsBySide[tmpSide] = [];
305
307
  }
308
+ tmpPortsBySide[tmpSide].push(tmpPort);
306
309
  }
307
310
 
308
311
  for (let tmpSide in tmpPortsBySide)
309
312
  {
310
313
  let tmpPorts = tmpPortsBySide[tmpSide];
314
+ // Determine the edge for label positioning
315
+ let tmpEdge = tmpGeometryProvider ? tmpGeometryProvider.getEdgeFromSide(tmpSide) : tmpSide;
316
+
311
317
  for (let i = 0; i < tmpPorts.length; i++)
312
318
  {
313
319
  let tmpPort = tmpPorts[i];
@@ -334,7 +340,8 @@ class PictViewFlowNode extends libPictView
334
340
  }
335
341
  pGroup.appendChild(tmpCircle);
336
342
 
337
- // Port label
343
+ // Port label — use the edge for alignment (all positions on the
344
+ // same edge share the same label direction)
338
345
  if (tmpPort.Label)
339
346
  {
340
347
  let tmpLabel = this._FlowView._SVGHelperProvider.createSVGElement('text');
@@ -345,62 +352,64 @@ class PictViewFlowNode extends libPictView
345
352
  let tmpLabelOffset = 12;
346
353
  let tmpPaddingExtra = tmpPortLabelPadding ? 8 : 0;
347
354
 
348
- if (tmpPortLabelsVertical)
355
+ // When PortLabelsOutside is true, labels render outside the node
356
+ // boundary (away from center) instead of inside (toward center).
357
+ // The direction multiplier flips the offset direction per edge.
358
+ let tmpOutsideFlip = tmpPortLabelsOutside ? -1 : 1;
359
+
360
+ if (tmpPortLabelsVertical)
349
361
  {
350
- // Vertical labels: rotated -90° and centered on the port position.
351
- // After rotation, text-anchor controls vertical centering, so 'middle'
352
- // ensures the label is centered next to its port circle.
353
- switch (tmpSide)
362
+ switch (tmpEdge)
354
363
  {
355
364
  case 'left':
356
- tmpLabel.setAttribute('x', String(tmpPosition.x + tmpLabelOffset + tmpPaddingExtra));
365
+ tmpLabel.setAttribute('x', String(tmpPosition.x + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
357
366
  tmpLabel.setAttribute('y', String(tmpPosition.y));
358
367
  tmpLabel.setAttribute('text-anchor', 'middle');
359
- tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x + tmpLabelOffset + tmpPaddingExtra}, ${tmpPosition.y})`);
368
+ tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip}, ${tmpPosition.y})`);
360
369
  break;
361
370
  case 'right':
362
- tmpLabel.setAttribute('x', String(tmpPosition.x - tmpLabelOffset - tmpPaddingExtra));
371
+ tmpLabel.setAttribute('x', String(tmpPosition.x - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
363
372
  tmpLabel.setAttribute('y', String(tmpPosition.y));
364
373
  tmpLabel.setAttribute('text-anchor', 'middle');
365
- tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x - tmpLabelOffset - tmpPaddingExtra}, ${tmpPosition.y})`);
374
+ tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip}, ${tmpPosition.y})`);
366
375
  break;
367
376
  case 'top':
368
377
  tmpLabel.setAttribute('x', String(tmpPosition.x));
369
- tmpLabel.setAttribute('y', String(tmpPosition.y + tmpLabelOffset + tmpPaddingExtra));
378
+ tmpLabel.setAttribute('y', String(tmpPosition.y + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
370
379
  tmpLabel.setAttribute('text-anchor', 'middle');
371
- tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x}, ${tmpPosition.y + tmpLabelOffset + tmpPaddingExtra})`);
380
+ tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x}, ${tmpPosition.y + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip})`);
372
381
  break;
373
382
  case 'bottom':
374
383
  tmpLabel.setAttribute('x', String(tmpPosition.x));
375
- tmpLabel.setAttribute('y', String(tmpPosition.y - tmpLabelOffset - tmpPaddingExtra));
384
+ tmpLabel.setAttribute('y', String(tmpPosition.y - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
376
385
  tmpLabel.setAttribute('text-anchor', 'middle');
377
- tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x}, ${tmpPosition.y - tmpLabelOffset - tmpPaddingExtra})`);
386
+ tmpLabel.setAttribute('transform', `rotate(-90, ${tmpPosition.x}, ${tmpPosition.y - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip})`);
378
387
  break;
379
388
  }
380
389
  }
381
390
  else
382
391
  {
383
392
  // Horizontal labels (default)
384
- switch (tmpSide)
393
+ switch (tmpEdge)
385
394
  {
386
395
  case 'left':
387
- tmpLabel.setAttribute('x', String(tmpPosition.x + tmpLabelOffset + tmpPaddingExtra));
396
+ tmpLabel.setAttribute('x', String(tmpPosition.x + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
388
397
  tmpLabel.setAttribute('y', String(tmpPosition.y));
389
- tmpLabel.setAttribute('text-anchor', 'start');
398
+ tmpLabel.setAttribute('text-anchor', tmpPortLabelsOutside ? 'end' : 'start');
390
399
  break;
391
400
  case 'right':
392
- tmpLabel.setAttribute('x', String(tmpPosition.x - tmpLabelOffset - tmpPaddingExtra));
401
+ tmpLabel.setAttribute('x', String(tmpPosition.x - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
393
402
  tmpLabel.setAttribute('y', String(tmpPosition.y));
394
- tmpLabel.setAttribute('text-anchor', 'end');
403
+ tmpLabel.setAttribute('text-anchor', tmpPortLabelsOutside ? 'start' : 'end');
395
404
  break;
396
405
  case 'top':
397
406
  tmpLabel.setAttribute('x', String(tmpPosition.x));
398
- tmpLabel.setAttribute('y', String(tmpPosition.y + tmpLabelOffset + tmpPaddingExtra));
407
+ tmpLabel.setAttribute('y', String(tmpPosition.y + (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
399
408
  tmpLabel.setAttribute('text-anchor', 'middle');
400
409
  break;
401
410
  case 'bottom':
402
411
  tmpLabel.setAttribute('x', String(tmpPosition.x));
403
- tmpLabel.setAttribute('y', String(tmpPosition.y - tmpLabelOffset - tmpPaddingExtra));
412
+ tmpLabel.setAttribute('y', String(tmpPosition.y - (tmpLabelOffset + tmpPaddingExtra) * tmpOutsideFlip));
404
413
  tmpLabel.setAttribute('text-anchor', 'middle');
405
414
  break;
406
415
  }