pict-section-flow 1.2.0 → 1.4.0

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": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Pict Section Flow Diagram",
5
5
  "main": "source/Pict-Section-Flow.js",
6
6
  "scripts": {
@@ -270,6 +270,29 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
270
270
  .pict-flow-node-title-bar-bottom {
271
271
  fill: var(--pf-node-title-bar-color);
272
272
  }
273
+ .pict-flow-node-resize-handle {
274
+ fill: var(--theme-color-brand-primary, #2880a6);
275
+ stroke: var(--theme-color-background-panel, #ffffff);
276
+ stroke-width: 1.5;
277
+ cursor: nwse-resize;
278
+ opacity: 0.85;
279
+ }
280
+ .pict-flow-node-resize-handle:hover { opacity: 1; }
281
+ .pict-flow-marquee {
282
+ fill: var(--theme-color-brand-primary, #2880a6);
283
+ fill-opacity: 0.10;
284
+ stroke: var(--theme-color-brand-primary, #2880a6);
285
+ stroke-width: 1;
286
+ stroke-dasharray: 4 3;
287
+ pointer-events: none;
288
+ }
289
+ .pict-flow-align-guide {
290
+ stroke: #e5397f;
291
+ stroke-width: 1;
292
+ stroke-dasharray: 3 2;
293
+ pointer-events: none;
294
+ shape-rendering: crispEdges;
295
+ }
273
296
  .pict-flow-node-title {
274
297
  fill: var(--pf-node-title-fill);
275
298
  font-size: var(--pf-node-title-size);
@@ -745,6 +768,19 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
745
768
  cursor: pointer;
746
769
  transition: stroke 0.15s;
747
770
  }
771
+ /* A connection's label (Data.Label), drawn at the midpoint. The white halo (paint-order) keeps it
772
+ legible where it crosses the line. */
773
+ .pict-flow-connection-label {
774
+ font-size: 12px;
775
+ font-family: inherit;
776
+ fill: var(--pf-text-primary, #2c3e50);
777
+ paint-order: stroke;
778
+ stroke: var(--theme-color-background-primary, #ffffff);
779
+ stroke-width: 3px;
780
+ stroke-linejoin: round;
781
+ pointer-events: none;
782
+ user-select: none;
783
+ }
748
784
  .pict-flow-connection:hover {
749
785
  stroke: var(--pf-connection-stroke-hover);
750
786
  stroke-width: 3;
@@ -1262,6 +1298,19 @@ class PictProviderFlowCSS extends libFableServiceProviderBase
1262
1298
  border-right: none;
1263
1299
  padding-right: 0;
1264
1300
  }
1301
+ /* The host-supplied extra-button group (ToolbarExtraButtons) renders even
1302
+ when empty; collapse it so consumers that pass no buttons see no gap. */
1303
+ .pict-flow-toolbar-group:empty {
1304
+ display: none;
1305
+ }
1306
+ .pict-flow-toolbar-btn-active {
1307
+ background-color: var(--pf-button-active-bg);
1308
+ border-color: var(--pf-button-hover-border);
1309
+ }
1310
+ /* An icon-only host button (ToolbarExtraButtons with no Label) renders an empty text span. */
1311
+ .pict-flow-toolbar-btn-text:empty {
1312
+ display: none;
1313
+ }
1265
1314
  .pict-flow-toolbar-btn {
1266
1315
  display: inline-flex;
1267
1316
  align-items: center;
@@ -465,6 +465,14 @@ class PictProviderFlowConnectorShapes extends libFableServiceProviderBase
465
465
  + '<polygon class="pict-flow-arrowhead pict-flow-arrowhead-tether" points="' + tmpTetherMarker.Points + '" fill="' + tmpTetherMarker.Fill + '" />'
466
466
  + '</marker>';
467
467
 
468
+ // Generic per-connection end markers, selectable per connection via Data.SourceMarker /
469
+ // Data.TargetMarker (a host like a moodboard styles its own edges). fill="context-stroke" makes
470
+ // each marker take the connection's own stroke color, so a recolored line recolors its markers.
471
+ tmpMarkup += '<marker id="flow-marker-arrow-end-' + pViewIdentifier + '" markerWidth="10" markerHeight="10" refX="8.5" refY="5" orient="auto" markerUnits="strokeWidth"><path d="M1,1 L9,5 L1,9 z" fill="context-stroke" /></marker>';
472
+ tmpMarkup += '<marker id="flow-marker-arrow-start-' + pViewIdentifier + '" markerWidth="10" markerHeight="10" refX="1.5" refY="5" orient="auto-start-reverse" markerUnits="strokeWidth"><path d="M1,1 L9,5 L1,9 z" fill="context-stroke" /></marker>';
473
+ tmpMarkup += '<marker id="flow-marker-dot-' + pViewIdentifier + '" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto" markerUnits="strokeWidth"><circle cx="4" cy="4" r="3" fill="context-stroke" /></marker>';
474
+ tmpMarkup += '<marker id="flow-marker-square-' + pViewIdentifier + '" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto" markerUnits="strokeWidth"><rect x="1" y="1" width="6" height="6" fill="context-stroke" /></marker>';
475
+
468
476
  return tmpMarkup;
469
477
  }
470
478
  }
@@ -45,6 +45,14 @@ const _DefaultIcons =
45
45
 
46
46
  // ── UI Icons ───────────────────────────────────────────────────────────
47
47
 
48
+ 'edit': '<svg xmlns="http://www.w3.org/2000/svg" width="{FlowIconSize}" height="{FlowIconSize}" viewBox="0 0 24 24" fill="none" stroke="var(--theme-color-text-primary, #2c3e50)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4z"/></svg>',
49
+
50
+ 'check': '<svg xmlns="http://www.w3.org/2000/svg" width="{FlowIconSize}" height="{FlowIconSize}" viewBox="0 0 24 24" fill="none" stroke="var(--theme-color-text-primary, #2c3e50)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>',
51
+
52
+ 'background': '<svg xmlns="http://www.w3.org/2000/svg" width="{FlowIconSize}" height="{FlowIconSize}" viewBox="0 0 24 24" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z" fill="var(--theme-color-background-secondary, #d5e8f7)" stroke="var(--theme-color-text-primary, #2c3e50)" stroke-width="2"/></svg>',
53
+
54
+ 'connect': '<svg xmlns="http://www.w3.org/2000/svg" width="{FlowIconSize}" height="{FlowIconSize}" viewBox="0 0 24 24" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M8 8l8 8" stroke="var(--theme-color-text-primary, #2c3e50)" stroke-width="2"/><circle cx="6" cy="6" r="3" fill="var(--theme-color-background-secondary, #d5e8f7)" stroke="var(--theme-color-text-primary, #2c3e50)" stroke-width="2"/><circle cx="18" cy="18" r="3" fill="var(--theme-color-background-secondary, #d5e8f7)" stroke="var(--theme-color-text-primary, #2c3e50)" stroke-width="2"/></svg>',
55
+
48
56
  'fullscreen': '<svg xmlns="http://www.w3.org/2000/svg" width="{FlowIconSize}" height="{FlowIconSize}" viewBox="0 0 24 24" fill="none" stroke="var(--theme-color-text-primary, #2c3e50)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6"/><path d="M9 21H3v-6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg>',
49
57
 
50
58
  'exit-fullscreen': '<svg xmlns="http://www.w3.org/2000/svg" width="{FlowIconSize}" height="{FlowIconSize}" viewBox="0 0 24 24" fill="none" stroke="var(--theme-color-text-primary, #2c3e50)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 14h6v6"/><path d="M20 10h-6V4"/><path d="M14 10l7-7"/><path d="M3 21l7-7"/></svg>',
@@ -116,12 +116,13 @@ class PictServiceFlowConnectionRenderer extends libFableServiceProviderBase
116
116
 
117
117
  // Hit area (wider invisible path for easier selection)
118
118
  let tmpShapeProvider = this._FlowView._ConnectorShapesProvider;
119
+ let tmpPathElement = null;
119
120
  if (tmpShapeProvider)
120
121
  {
121
122
  let tmpHitArea = tmpShapeProvider.createConnectionHitAreaElement(tmpPath, pConnection.Hash);
122
123
  pConnectionsLayer.appendChild(tmpHitArea);
123
124
 
124
- let tmpPathElement = tmpShapeProvider.createConnectionPathElement(
125
+ tmpPathElement = tmpShapeProvider.createConnectionPathElement(
125
126
  tmpPath, pConnection.Hash, pIsSelected, tmpViewIdentifier);
126
127
  if (tmpConnTypeClass)
127
128
  {
@@ -145,7 +146,7 @@ class PictServiceFlowConnectionRenderer extends libFableServiceProviderBase
145
146
  tmpHitArea.setAttribute('data-element-type', 'connection-hitarea');
146
147
  pConnectionsLayer.appendChild(tmpHitArea);
147
148
 
148
- let tmpPathElement = this._FlowView._SVGHelperProvider.createSVGElement('path');
149
+ tmpPathElement = this._FlowView._SVGHelperProvider.createSVGElement('path');
149
150
  tmpPathElement.setAttribute('class', `pict-flow-connection${tmpConnTypeClass} ${pIsSelected ? 'selected' : ''}`);
150
151
  tmpPathElement.setAttribute('d', tmpPath);
151
152
  tmpPathElement.setAttribute('data-connection-hash', pConnection.Hash);
@@ -160,14 +161,25 @@ class PictServiceFlowConnectionRenderer extends libFableServiceProviderBase
160
161
  pConnectionsLayer.appendChild(tmpPathElement);
161
162
  }
162
163
 
164
+ // Per-connection host styling (a moodboard styles its own edges): stroke color / width / dash and
165
+ // the end markers, applied only when the connection's Data carries them so default (workflow)
166
+ // connections are untouched. A label, when set, is drawn at the midpoint.
167
+ let tmpHasMarkers = (typeof tmpData.SourceMarker !== 'undefined' || typeof tmpData.TargetMarker !== 'undefined');
168
+ this._applyConnectionStyle(tmpPathElement, tmpData, tmpViewIdentifier);
169
+ this._renderConnectionLabel(pConnection, tmpData, pConnectionsLayer, tmpSourcePos, tmpTargetPos);
170
+
163
171
  // Render the colored endpoint dot at each end of the connection
164
172
  // into the dedicated endpoints layer (sits *above* the nodes
165
173
  // layer so the dot doesn't get hidden under the card chrome
166
174
  // when an edge theme places it on the node's perimeter).
167
175
  // PortRenderer suppresses its own circle for any port that
168
- // participates in a connection — we own the dot here.
176
+ // participates in a connection — we own the dot here. Skipped when the
177
+ // connection supplies its own end markers (those own the endpoints).
169
178
  let tmpEndpointsLayer = this._FlowView._EndpointsLayer || pConnectionsLayer;
170
- this._renderEndpointDots(pConnection, tmpEndpointsLayer, tmpSourcePos, tmpTargetPos);
179
+ if (!tmpHasMarkers)
180
+ {
181
+ this._renderEndpointDots(pConnection, tmpEndpointsLayer, tmpSourcePos, tmpTargetPos);
182
+ }
171
183
 
172
184
  // When the resolved attachment differs from the card-defined port
173
185
  // position (e.g. a Perimeter theme moved the dot to the edge of the
@@ -187,6 +199,66 @@ class PictServiceFlowConnectionRenderer extends libFableServiceProviderBase
187
199
  }
188
200
  }
189
201
 
202
+ // Apply per-connection host styling to the path element from the connection's Data: StrokeColor,
203
+ // StrokeWidth, StrokeStyle ('solid' | 'dashed' | 'dotted') and SourceMarker / TargetMarker
204
+ // ('none' | 'arrow' | 'dot' | 'square'). Each is applied only when present, so connections that do
205
+ // not carry these keys render exactly as before.
206
+ _applyConnectionStyle(pPathElement, pData, pViewIdentifier)
207
+ {
208
+ if (!pPathElement || !pData || !pPathElement.style) { return; }
209
+ // Stroke color / width / dash go through inline style, NOT SVG presentation attributes: the
210
+ // .pict-flow-connection CSS rule sets stroke + stroke-width, and a stylesheet rule beats a
211
+ // presentation attribute, so an attribute would be silently ignored. Inline style outranks the
212
+ // stylesheet, so the per-connection appearance wins.
213
+ if (pData.StrokeColor) { pPathElement.style.stroke = pData.StrokeColor; }
214
+ if (pData.StrokeWidth) { pPathElement.style.strokeWidth = String(pData.StrokeWidth); }
215
+ if (pData.StrokeStyle)
216
+ {
217
+ pPathElement.style.strokeDasharray = (pData.StrokeStyle === 'dashed') ? '7,5' : ((pData.StrokeStyle === 'dotted') ? '1.5,4' : 'none');
218
+ }
219
+ // Markers are not styled by CSS, so attributes are correct (they reference the marker defs).
220
+ if (typeof pData.TargetMarker !== 'undefined')
221
+ {
222
+ let tmpEnd = this._connectionMarkerId(pData.TargetMarker, 'end', pViewIdentifier);
223
+ if (tmpEnd) { pPathElement.setAttribute('marker-end', 'url(#' + tmpEnd + ')'); }
224
+ else { pPathElement.removeAttribute('marker-end'); }
225
+ }
226
+ if (typeof pData.SourceMarker !== 'undefined')
227
+ {
228
+ let tmpStart = this._connectionMarkerId(pData.SourceMarker, 'start', pViewIdentifier);
229
+ if (tmpStart) { pPathElement.setAttribute('marker-start', 'url(#' + tmpStart + ')'); }
230
+ else { pPathElement.removeAttribute('marker-start'); }
231
+ }
232
+ }
233
+
234
+ // Resolve a marker name + end ('start' | 'end') to its SVG marker def id; null for 'none'/unknown.
235
+ _connectionMarkerId(pMarker, pEnd, pViewIdentifier)
236
+ {
237
+ if (pMarker === 'arrow') { return 'flow-marker-arrow-' + pEnd + '-' + pViewIdentifier; }
238
+ if (pMarker === 'dot') { return 'flow-marker-dot-' + pViewIdentifier; }
239
+ if (pMarker === 'square') { return 'flow-marker-square-' + pViewIdentifier; }
240
+ return null;
241
+ }
242
+
243
+ // Draw a connection's label (Data.Label) at the midpoint of its endpoints. A white halo (paint-order
244
+ // stroke, set in CSS) keeps it legible over the line. Skipped when there is no label.
245
+ _renderConnectionLabel(pConnection, pData, pLayer, pSourcePos, pTargetPos)
246
+ {
247
+ // Render when a Label key is present (even ''), so a host that edits the label in place has an
248
+ // element to update; a connection with no Label key (a default workflow edge) gets none.
249
+ if (!pData || typeof pData.Label === 'undefined' || !pSourcePos || !pTargetPos) { return; }
250
+ if (!this._FlowView._SVGHelperProvider) { return; }
251
+ let tmpText = this._FlowView._SVGHelperProvider.createSVGElement('text');
252
+ tmpText.setAttribute('class', 'pict-flow-connection-label');
253
+ tmpText.setAttribute('x', String((pSourcePos.x + pTargetPos.x) / 2));
254
+ tmpText.setAttribute('y', String((pSourcePos.y + pTargetPos.y) / 2));
255
+ tmpText.setAttribute('text-anchor', 'middle');
256
+ tmpText.setAttribute('dominant-baseline', 'middle');
257
+ tmpText.setAttribute('data-connection-hash', pConnection.Hash);
258
+ tmpText.textContent = pData.Label;
259
+ pLayer.appendChild(tmpText);
260
+ }
261
+
190
262
  /**
191
263
  * Append the colored endpoint dots for both ends of a connection
192
264
  * onto the destination layer (absolute coords). Reuses
@@ -106,7 +106,7 @@ class PictServiceFlowDataManager extends libFableServiceProviderBase
106
106
  OpenPanels: Array.isArray(pFlowData.OpenPanels) ? pFlowData.OpenPanels : [],
107
107
  SavedLayouts: Array.isArray(pFlowData.SavedLayouts) ? pFlowData.SavedLayouts : [],
108
108
  ViewState: Object.assign(
109
- { PanX: 0, PanY: 0, Zoom: 1, SelectedNodeHash: null, SelectedConnectionHash: null, SelectedTetherHash: null },
109
+ { PanX: 0, PanY: 0, Zoom: 1, SelectedNodeHash: null, SelectedNodeHashes: [], SelectedConnectionHash: null, SelectedTetherHash: null },
110
110
  pFlowData.ViewState || {}
111
111
  ),
112
112
  LayoutAlgorithm: (typeof pFlowData.LayoutAlgorithm === 'string' && pFlowData.LayoutAlgorithm !== '') ? pFlowData.LayoutAlgorithm : tmpDefaultAlgorithm,