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 +1 -1
- package/source/providers/PictProvider-Flow-CSS.js +49 -0
- package/source/providers/PictProvider-Flow-ConnectorShapes.js +8 -0
- package/source/providers/PictProvider-Flow-Icons.js +8 -0
- package/source/services/PictService-Flow-ConnectionRenderer.js +76 -4
- package/source/services/PictService-Flow-DataManager.js +1 -1
- package/source/services/PictService-Flow-InteractionManager.js +358 -24
- package/source/services/PictService-Flow-RenderManager.js +3 -1
- package/source/services/PictService-Flow-SelectionManager.js +86 -5
- package/source/views/PictView-Flow-FloatingToolbar.js +53 -0
- package/source/views/PictView-Flow-Node.js +56 -2
- package/source/views/PictView-Flow-PropertiesPanel.js +27 -5
- package/source/views/PictView-Flow-Toolbar.js +99 -11
- package/source/views/PictView-Flow.js +85 -9
- package/test/CardPalette_tests.js +43 -0
- package/test/ConnectionStyle_tests.js +90 -0
- package/test/InteractionManager_tests.js +279 -0
- package/test/NodeView_tests.js +17 -0
- package/test/SelectionManager_tests.js +185 -0
- package/test/ToolbarExtraButtons_tests.js +138 -0
- package/test/UndirectedConnections_tests.js +70 -0
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|