pict-section-flow 1.3.0 → 2.0.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.
Files changed (162) hide show
  1. package/package.json +7 -2
  2. package/source/Pict-Section-Flow.js +20 -14
  3. package/source/providers/PictProvider-Flow-Background.js +303 -0
  4. package/source/providers/PictProvider-Flow-CSS.js +99 -7
  5. package/source/providers/PictProvider-Flow-ConnectorShapes.js +8 -0
  6. package/source/providers/PictProvider-Flow-Geometry.js +11 -421
  7. package/source/providers/PictProvider-Flow-Icons.js +20 -0
  8. package/source/providers/PictProvider-Flow-Layouts.js +107 -0
  9. package/source/services/PictService-Flow-ConnectionRenderer.js +77 -5
  10. package/source/services/PictService-Flow-CursorManager.js +113 -0
  11. package/source/services/PictService-Flow-InteractionManager.js +443 -61
  12. package/source/services/PictService-Flow-Layout.js +21 -16
  13. package/source/services/PictService-Flow-PathGenerator.js +30 -417
  14. package/source/services/PictService-Flow-RenderManager.js +9 -1
  15. package/source/services/PictService-Flow-ViewportManager.js +102 -0
  16. package/source/views/PictView-Flow-FloatingToolbar.js +57 -0
  17. package/source/views/PictView-Flow-Node.js +36 -0
  18. package/source/views/PictView-Flow-PropertiesPanel.js +27 -5
  19. package/source/views/PictView-Flow-Toolbar.js +148 -13
  20. package/source/views/PictView-Flow.js +628 -3
  21. package/.claude/launch.json +0 -11
  22. package/docs/.nojekyll +0 -0
  23. package/docs/Architecture.md +0 -163
  24. package/docs/Custom-Styling.md +0 -275
  25. package/docs/Data_Model.md +0 -149
  26. package/docs/Event_System.md +0 -156
  27. package/docs/Getting_Started.md +0 -237
  28. package/docs/Implementation_Reference.md +0 -528
  29. package/docs/Layout_Persistence.md +0 -117
  30. package/docs/README.md +0 -103
  31. package/docs/Theme_Integration.md +0 -150
  32. package/docs/_brand.json +0 -18
  33. package/docs/_cover.md +0 -17
  34. package/docs/_playground.json +0 -24
  35. package/docs/_sidebar.md +0 -57
  36. package/docs/_topbar.md +0 -8
  37. package/docs/_version.json +0 -7
  38. package/docs/api/PictFlowCard.md +0 -216
  39. package/docs/api/PictFlowCardPropertiesPanel.md +0 -235
  40. package/docs/api/addConnection.md +0 -101
  41. package/docs/api/addNode.md +0 -137
  42. package/docs/api/autoLayout.md +0 -77
  43. package/docs/api/getFlowData.md +0 -112
  44. package/docs/api/marshalToView.md +0 -95
  45. package/docs/api/openPanel.md +0 -128
  46. package/docs/api/registerHandler.md +0 -174
  47. package/docs/api/registerNodeType.md +0 -142
  48. package/docs/api/removeConnection.md +0 -57
  49. package/docs/api/removeNode.md +0 -80
  50. package/docs/api/saveLayout.md +0 -152
  51. package/docs/api/screenToSVGCoords.md +0 -68
  52. package/docs/api/selectNode.md +0 -116
  53. package/docs/api/setTheme.md +0 -168
  54. package/docs/api/setZoom.md +0 -97
  55. package/docs/api/toggleFullscreen.md +0 -68
  56. package/docs/card-help/EACH.md +0 -19
  57. package/docs/card-help/FREAD.md +0 -24
  58. package/docs/card-help/FWRITE.md +0 -24
  59. package/docs/card-help/GET.md +0 -22
  60. package/docs/card-help/ITE.md +0 -23
  61. package/docs/card-help/LOG.md +0 -23
  62. package/docs/card-help/NOTE.md +0 -17
  63. package/docs/card-help/PREV.md +0 -18
  64. package/docs/card-help/SET.md +0 -27
  65. package/docs/card-help/SPKL.md +0 -22
  66. package/docs/card-help/STAT.md +0 -23
  67. package/docs/card-help/SW.md +0 -25
  68. package/docs/diagrams/architecture-at-a-glance.excalidraw +0 -4270
  69. package/docs/diagrams/architecture-at-a-glance.mmd +0 -30
  70. package/docs/diagrams/architecture-at-a-glance.svg +0 -2
  71. package/docs/diagrams/data-flow.excalidraw +0 -1451
  72. package/docs/diagrams/data-flow.mmd +0 -17
  73. package/docs/diagrams/data-flow.svg +0 -2
  74. package/docs/diagrams/high-level-design.excalidraw +0 -5767
  75. package/docs/diagrams/high-level-design.mmd +0 -86
  76. package/docs/diagrams/high-level-design.svg +0 -2
  77. package/docs/diagrams/relationships.excalidraw +0 -3852
  78. package/docs/diagrams/relationships.mmd +0 -9
  79. package/docs/diagrams/relationships.svg +0 -2
  80. package/docs/diagrams/service-initialization-sequence.excalidraw +0 -1466
  81. package/docs/diagrams/service-initialization-sequence.mmd +0 -19
  82. package/docs/diagrams/service-initialization-sequence.svg +0 -2
  83. package/docs/diagrams/svg-layer-structure.excalidraw +0 -1060
  84. package/docs/diagrams/svg-layer-structure.mmd +0 -18
  85. package/docs/diagrams/svg-layer-structure.svg +0 -2
  86. package/docs/examples/README.md +0 -9
  87. package/docs/examples/simple_cards/README.md +0 -677
  88. package/docs/examples/simple_cards/css/flowexample.css +0 -65
  89. package/docs/examples/simple_cards/index.html +0 -32
  90. package/docs/examples/simple_cards/js/pict.min.js +0 -12
  91. package/docs/examples/simple_cards/pict-section-flow-example-simple-cards.compatible.min.js +0 -1
  92. package/docs/index.html +0 -38
  93. package/docs/playground/app.json +0 -6
  94. package/docs/playground/appdata.json +0 -85
  95. package/docs/playground/application.js +0 -23
  96. package/docs/playground/pict.json +0 -17
  97. package/docs/playground/runtime/pict-application.min.js +0 -2
  98. package/docs/playground/runtime/pict-section-flow.min.js +0 -2
  99. package/docs/playground/runtime/pict-section-modal.min.js +0 -2
  100. package/docs/playground/runtime/pict.min.js +0 -12
  101. package/docs/retold-catalog.json +0 -244
  102. package/docs/retold-keyword-index.json +0 -26028
  103. package/example_applications/simple_cards/css/flowexample.css +0 -65
  104. package/example_applications/simple_cards/html/index.html +0 -32
  105. package/example_applications/simple_cards/package.json +0 -52
  106. package/example_applications/simple_cards/source/Pict-Application-FlowExample-Configuration.json +0 -15
  107. package/example_applications/simple_cards/source/Pict-Application-FlowExample.js +0 -539
  108. package/example_applications/simple_cards/source/card-help-content.js +0 -16
  109. package/example_applications/simple_cards/source/cards/FlowCard-Comment.js +0 -38
  110. package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +0 -44
  111. package/example_applications/simple_cards/source/cards/FlowCard-Each.js +0 -38
  112. package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +0 -56
  113. package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +0 -50
  114. package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +0 -37
  115. package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +0 -49
  116. package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +0 -55
  117. package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +0 -97
  118. package/example_applications/simple_cards/source/cards/FlowCard-Sparkline.js +0 -100
  119. package/example_applications/simple_cards/source/cards/FlowCard-StatusMonitor.js +0 -46
  120. package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +0 -39
  121. package/example_applications/simple_cards/source/providers/PictRouter-FlowExample-Configuration.json +0 -22
  122. package/example_applications/simple_cards/source/sample-flows.js +0 -410
  123. package/example_applications/simple_cards/source/views/PictView-FlowExample-About.js +0 -184
  124. package/example_applications/simple_cards/source/views/PictView-FlowExample-BottomBar.js +0 -77
  125. package/example_applications/simple_cards/source/views/PictView-FlowExample-Documentation.js +0 -325
  126. package/example_applications/simple_cards/source/views/PictView-FlowExample-FileWriteInfo.js +0 -59
  127. package/example_applications/simple_cards/source/views/PictView-FlowExample-Layout.js +0 -90
  128. package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +0 -453
  129. package/example_applications/simple_cards/source/views/PictView-FlowExample-TopBar.js +0 -95
  130. package/scripts/generate-card-help.js +0 -214
  131. package/source/providers/edges/Edge-Bezier.js +0 -41
  132. package/source/providers/edges/Edge-Orthogonal.js +0 -37
  133. package/source/providers/edges/Edge-OrthogonalSnap.js +0 -72
  134. package/source/providers/edges/Edge-Perimeter-Linear.js +0 -31
  135. package/source/providers/edges/Edge-Perimeter-Orthogonal.js +0 -39
  136. package/source/providers/edges/Edge-Perimeter.js +0 -48
  137. package/source/providers/edges/Edge-PerimeterMath.js +0 -92
  138. package/source/providers/edges/Edge-Straight.js +0 -24
  139. package/source/providers/layouts/Layout-Circular.js +0 -203
  140. package/source/providers/layouts/Layout-Coerce.js +0 -40
  141. package/source/providers/layouts/Layout-Columnar.js +0 -134
  142. package/source/providers/layouts/Layout-Custom.js +0 -27
  143. package/source/providers/layouts/Layout-ForcedFromCenter.js +0 -256
  144. package/source/providers/layouts/Layout-Grid.js +0 -134
  145. package/source/providers/layouts/Layout-Layered.js +0 -155
  146. package/source/providers/layouts/Layout-Rank.js +0 -141
  147. package/source/providers/layouts/Layout-Staggered.js +0 -131
  148. package/source/providers/layouts/Layout-Tabular.js +0 -94
  149. package/test/ConnectionHandleManager_tests.js +0 -717
  150. package/test/ConnectionRenderer_tests.js +0 -591
  151. package/test/DataManager_tests.js +0 -859
  152. package/test/Geometry_tests.js +0 -767
  153. package/test/InteractionManager_tests.js +0 -279
  154. package/test/Layout_tests.js +0 -1604
  155. package/test/NodeView_tests.js +0 -66
  156. package/test/PanelManager_tests.js +0 -172
  157. package/test/PathGenerator_tests.js +0 -978
  158. package/test/PortRenderer_tests.js +0 -376
  159. package/test/RenderManager_tests.js +0 -756
  160. package/test/Renderer_tests.js +0 -133
  161. package/test/SelectionManager_tests.js +0 -185
  162. package/test/StylePresets_tests.js +0 -153
@@ -13,6 +13,9 @@ const INTERACTION_STATES =
13
13
  PANNING: 'panning',
14
14
  RESIZING_PANEL: 'resizing-panel',
15
15
  RESIZING_NODE: 'resizing-node',
16
+ ROTATING_NODE: 'rotating-node',
17
+ RESIZING_FRAME: 'resizing-frame',
18
+ MOVING_FRAME: 'moving-frame',
16
19
  MARQUEE: 'marquee'
17
20
  };
18
21
 
@@ -30,7 +33,7 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
30
33
  this._ViewportElement = null;
31
34
 
32
35
  // Interaction state
33
- this._State = INTERACTION_STATES.IDLE;
36
+ this._setState(INTERACTION_STATES.IDLE);
34
37
 
35
38
  // Drag state
36
39
  this._DragNodeHash = null;
@@ -80,6 +83,13 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
80
83
  this._ResizeNodeStartWidth = 0;
81
84
  this._ResizeNodeStartHeight = 0;
82
85
 
86
+ // Content-frame edit state (drag the frame's edge handles to resize, or its move
87
+ // handle to reposition the view-area box).
88
+ this._FrameDragEdge = null;
89
+ this._FrameStart = null;
90
+ this._FrameStartPointerX = 0;
91
+ this._FrameStartPointerY = 0;
92
+
83
93
  // Double-click detection for connections
84
94
  this._LastConnectionClickTime = 0;
85
95
  this._LastConnectionClickHash = null;
@@ -133,6 +143,9 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
133
143
  {
134
144
  pEvent.preventDefault();
135
145
 
146
+ // No edge/handle editing in read-only mode.
147
+ if (this._isReadOnly()) return;
148
+
136
149
  let tmpTarget = pEvent.target;
137
150
  let tmpElementType = this._getElementType(tmpTarget);
138
151
 
@@ -168,6 +181,53 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
168
181
  document.removeEventListener('keydown', this._boundOnKeyDown);
169
182
  }
170
183
 
184
+ /**
185
+ * Whether the flow is in read-only / presentation mode. Tolerant of minimal
186
+ * test harnesses: prefers the view's isReadOnly(), falls back to the option.
187
+ * @returns {boolean}
188
+ */
189
+ _isReadOnly()
190
+ {
191
+ if (!this._FlowView) return false;
192
+ if (typeof this._FlowView.isReadOnly === 'function') return !!this._FlowView.isReadOnly();
193
+ return !!(this._FlowView.options && this._FlowView.options.ReadOnly);
194
+ }
195
+
196
+ /**
197
+ * Whether read-only canvas navigation (pan + wheel-zoom) is currently on (the
198
+ * "hand" toggle). When off, a read-only canvas is static and the wheel scrolls
199
+ * the page.
200
+ * @returns {boolean}
201
+ */
202
+ _isReadOnlyNavigating()
203
+ {
204
+ return !!(this._FlowView && typeof this._FlowView.isReadOnlyNavigation === 'function' && this._FlowView.isReadOnlyNavigation());
205
+ }
206
+
207
+ /**
208
+ * The single interaction-state transition chokepoint. Setting state here keeps
209
+ * the canvas cursor (and any future state-derived concern) an intentional,
210
+ * derived property rather than something toggled by hand at each call site.
211
+ * @param {string} pState - an INTERACTION_STATES value
212
+ */
213
+ _setState(pState)
214
+ {
215
+ this._State = pState;
216
+ this._updateCursor();
217
+ }
218
+
219
+ /**
220
+ * Ask the CursorManager to re-derive the canvas cursor from the current state
221
+ * and mode. No-op until the CursorManager and SVG element exist.
222
+ */
223
+ _updateCursor()
224
+ {
225
+ if (this._FlowView && this._FlowView._CursorManager)
226
+ {
227
+ this._FlowView._CursorManager.update();
228
+ }
229
+ }
230
+
171
231
  /**
172
232
  * Handle pointer down event
173
233
  * @param {PointerEvent} pEvent
@@ -192,6 +252,59 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
192
252
  this._SVGElement.setPointerCapture(pEvent.pointerId);
193
253
  }
194
254
 
255
+ // Read-only / presentation mode: a reduced dispatch. Selection, node
256
+ // activation (so a host can open a read-only inspector), and panning are
257
+ // allowed; every editing interaction (drag, resize, connect, handle add,
258
+ // panel edit) is inert.
259
+ if (this._isReadOnly())
260
+ {
261
+ // Hand tool on (navigation enabled): a drag anywhere grabs and pans the canvas, including
262
+ // over a card — you are moving the viewport, like the Photoshop hand. No selection here.
263
+ if (this._isReadOnlyNavigating())
264
+ {
265
+ if (pEvent.button === 0 && this._FlowView.options.EnablePanning)
266
+ {
267
+ this._startPanning(pEvent);
268
+ }
269
+ return;
270
+ }
271
+
272
+ // Hand off (static): selection + node activation for inspection; the canvas does not move.
273
+ switch (tmpElementType)
274
+ {
275
+ case 'node':
276
+ case 'node-body':
277
+ case 'panel-indicator':
278
+ {
279
+ let tmpNodeHash = this._getNodeHash(tmpTarget);
280
+ if (tmpNodeHash)
281
+ {
282
+ this._FlowView.selectNode(tmpNodeHash);
283
+ let tmpNode = this._FlowView.getNode(tmpNodeHash);
284
+ if (tmpNode && this._FlowView._EventHandlerProvider)
285
+ {
286
+ this._FlowView._EventHandlerProvider.fireEvent('onNodeActivate', tmpNode);
287
+ }
288
+ }
289
+ break;
290
+ }
291
+
292
+ case 'connection':
293
+ case 'connection-hitarea':
294
+ this._selectConnection(tmpTarget);
295
+ break;
296
+
297
+ case 'tether':
298
+ case 'tether-hitarea':
299
+ this._selectTether(tmpTarget);
300
+ break;
301
+
302
+ default:
303
+ break;
304
+ }
305
+ return;
306
+ }
307
+
195
308
  switch (tmpElementType)
196
309
  {
197
310
  case 'port':
@@ -245,6 +358,18 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
245
358
  this._startNodeResize(pEvent, tmpTarget);
246
359
  break;
247
360
 
361
+ case 'node-rotate':
362
+ this._startNodeRotate(pEvent, tmpTarget);
363
+ break;
364
+
365
+ case 'frame-resize':
366
+ this._startFrameResize(pEvent, tmpTarget);
367
+ break;
368
+
369
+ case 'frame-move':
370
+ this._startFrameMove(pEvent, tmpTarget);
371
+ break;
372
+
248
373
  case 'panel-close':
249
374
  {
250
375
  let tmpPanelHash = this._getPanelHash(tmpTarget);
@@ -417,6 +542,18 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
417
542
  this._onNodeResize(pEvent);
418
543
  break;
419
544
 
545
+ case INTERACTION_STATES.RESIZING_FRAME:
546
+ this._onFrameResize(pEvent);
547
+ break;
548
+
549
+ case INTERACTION_STATES.MOVING_FRAME:
550
+ this._onFrameMove(pEvent);
551
+ break;
552
+
553
+ case INTERACTION_STATES.ROTATING_NODE:
554
+ this._onNodeRotate(pEvent);
555
+ break;
556
+
420
557
  case INTERACTION_STATES.PANNING:
421
558
  this._onPan(pEvent);
422
559
  break;
@@ -463,6 +600,15 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
463
600
  this._endNodeResize(pEvent);
464
601
  break;
465
602
 
603
+ case INTERACTION_STATES.RESIZING_FRAME:
604
+ case INTERACTION_STATES.MOVING_FRAME:
605
+ this._endFrameEdit(pEvent);
606
+ break;
607
+
608
+ case INTERACTION_STATES.ROTATING_NODE:
609
+ this._endNodeRotate(pEvent);
610
+ break;
611
+
466
612
  case INTERACTION_STATES.CONNECTING:
467
613
  this._endConnection(pEvent);
468
614
  break;
@@ -478,24 +624,98 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
478
624
  }
479
625
 
480
626
  /**
481
- * Handle mouse wheel for zoom
627
+ * Decide what a wheel event should do, from config and modifier keys. Pure
628
+ * (no DOM, no side effects) so it is unit testable.
629
+ *
630
+ * WheelMode 'zoom' (default): wheel zooms; if WheelZoomRequiresModifier is on,
631
+ * a plain wheel pans and ctrl/cmd+wheel zooms.
632
+ * WheelMode 'pan': wheel pans; ctrl/cmd+wheel zooms.
633
+ * WheelMode 'none': flow ignores the wheel (page scrolls).
634
+ *
635
+ * Zoom is gated by EnableZooming and pan by EnablePanning; when the intended
636
+ * action is disabled the result is 'none' (matching the legacy early return
637
+ * when zooming was off).
638
+ *
639
+ * @param {WheelEvent} pEvent
640
+ * @returns {{action: string}} action is 'zoom' | 'pan' | 'none'
641
+ */
642
+ _resolveWheelAction(pEvent)
643
+ {
644
+ let tmpOptions = (this._FlowView && this._FlowView.options) ? this._FlowView.options : {};
645
+
646
+ // Read-only has its own wheel policy: by default the wheel scrolls the page
647
+ // (the canvas is static content); only when navigation is toggled on (the
648
+ // hand toggle) does the wheel zoom the canvas.
649
+ if (this._isReadOnly())
650
+ {
651
+ return { action: this._isReadOnlyNavigating() ? 'zoom' : 'none' };
652
+ }
653
+
654
+ let tmpWheelMode = tmpOptions.WheelMode || 'zoom';
655
+
656
+ if (tmpWheelMode === 'none')
657
+ {
658
+ return { action: 'none' };
659
+ }
660
+
661
+ let tmpModifier = !!(pEvent && (pEvent.ctrlKey || pEvent.metaKey));
662
+
663
+ let tmpWantZoom;
664
+ if (tmpWheelMode === 'pan')
665
+ {
666
+ tmpWantZoom = tmpModifier;
667
+ }
668
+ else
669
+ {
670
+ tmpWantZoom = tmpOptions.WheelZoomRequiresModifier ? tmpModifier : true;
671
+ }
672
+
673
+ if (tmpWantZoom)
674
+ {
675
+ return { action: tmpOptions.EnableZooming ? 'zoom' : 'none' };
676
+ }
677
+
678
+ return { action: (tmpOptions.EnablePanning === false) ? 'none' : 'pan' };
679
+ }
680
+
681
+ /**
682
+ * Handle mouse wheel: zoom toward the pointer, pan the canvas, or ignore,
683
+ * per _resolveWheelAction.
482
684
  * @param {WheelEvent} pEvent
483
685
  */
484
686
  _onWheel(pEvent)
485
687
  {
486
- if (!this._FlowView || !this._FlowView.options.EnableZooming) return;
688
+ if (!this._FlowView) return;
689
+
690
+ let tmpResolved = this._resolveWheelAction(pEvent);
691
+ if (tmpResolved.action === 'none') return;
487
692
 
488
693
  pEvent.preventDefault();
489
694
 
490
- let tmpDelta = pEvent.deltaY > 0 ? -this._FlowView.options.ZoomStep : this._FlowView.options.ZoomStep;
491
- let tmpNewZoom = this._FlowView.viewState.Zoom + tmpDelta;
695
+ let tmpOptions = this._FlowView.options;
492
696
 
493
- // Zoom toward mouse position
494
- let tmpRect = this._SVGElement.getBoundingClientRect();
495
- let tmpMouseX = pEvent.clientX - tmpRect.left;
496
- let tmpMouseY = pEvent.clientY - tmpRect.top;
697
+ if (tmpResolved.action === 'zoom')
698
+ {
699
+ let tmpSensitivity = (typeof tmpOptions.WheelZoomSensitivity === 'number') ? tmpOptions.WheelZoomSensitivity : 1;
700
+ let tmpStep = tmpOptions.ZoomStep * tmpSensitivity;
701
+ let tmpDelta = pEvent.deltaY > 0 ? -tmpStep : tmpStep;
702
+ let tmpNewZoom = this._FlowView.viewState.Zoom + tmpDelta;
703
+
704
+ // Zoom toward mouse position
705
+ let tmpRect = this._SVGElement.getBoundingClientRect();
706
+ let tmpMouseX = pEvent.clientX - tmpRect.left;
707
+ let tmpMouseY = pEvent.clientY - tmpRect.top;
497
708
 
498
- this._FlowView.setZoom(tmpNewZoom, tmpMouseX, tmpMouseY);
709
+ this._FlowView.setZoom(tmpNewZoom, tmpMouseX, tmpMouseY);
710
+ return;
711
+ }
712
+
713
+ // Pan: wheel scrolls the canvas. Shift+wheel commonly arrives as deltaX.
714
+ let tmpPanSensitivity = (typeof tmpOptions.WheelPanSensitivity === 'number') ? tmpOptions.WheelPanSensitivity : 1;
715
+ if (this._FlowView._ViewportManager && typeof this._FlowView._ViewportManager.panBy === 'function')
716
+ {
717
+ this._FlowView._ViewportManager.panBy(-pEvent.deltaX * tmpPanSensitivity, -pEvent.deltaY * tmpPanSensitivity);
718
+ }
499
719
  }
500
720
 
501
721
  /**
@@ -509,6 +729,9 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
509
729
  // Only handle events when the flow is focused/visible
510
730
  if (pEvent.key === 'Delete' || pEvent.key === 'Backspace')
511
731
  {
732
+ // No deletion in read-only mode.
733
+ if (this._isReadOnly()) return;
734
+
512
735
  // Don't delete if user is typing in an input or inside a panel
513
736
  if (pEvent.target && (pEvent.target.tagName === 'INPUT' || pEvent.target.tagName === 'TEXTAREA' || pEvent.target.tagName === 'SELECT'))
514
737
  {
@@ -579,13 +802,11 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
579
802
  }
580
803
  if (this._DragNodes.length === 0) return;
581
804
 
582
- this._State = INTERACTION_STATES.DRAGGING_NODE;
805
+ this._setState(INTERACTION_STATES.DRAGGING_NODE);
583
806
  this._DragNodeHash = tmpNodeHash;
584
807
  this._DragStartX = pEvent.clientX;
585
808
  this._DragStartY = pEvent.clientY;
586
809
 
587
- this._SVGElement.classList.add('panning');
588
-
589
810
  for (let i = 0; i < this._DragNodes.length; i++)
590
811
  {
591
812
  let tmpNodeGroup = this._FlowView._NodesLayer.querySelector(`[data-node-hash="${this._DragNodes[i].Hash}"]`);
@@ -735,9 +956,7 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
735
956
  }
736
957
 
737
958
  _endNodeDrag(pEvent)
738
- {
739
- this._SVGElement.classList.remove('panning');
740
- this._clearGuides();
959
+ { this._clearGuides();
741
960
 
742
961
  let tmpDragged = this._DragNodes || [];
743
962
  for (let i = 0; i < tmpDragged.length; i++)
@@ -759,7 +978,7 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
759
978
  this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowView.flowData);
760
979
  }
761
980
 
762
- this._State = INTERACTION_STATES.IDLE;
981
+ this._setState(INTERACTION_STATES.IDLE);
763
982
  this._DragNodeHash = null;
764
983
  this._DragNodes = null;
765
984
  }
@@ -774,14 +993,12 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
774
993
  let tmpPanel = this._FlowView._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === tmpPanelHash);
775
994
  if (!tmpPanel) return;
776
995
 
777
- this._State = INTERACTION_STATES.DRAGGING_PANEL;
996
+ this._setState(INTERACTION_STATES.DRAGGING_PANEL);
778
997
  this._DragPanelHash = tmpPanelHash;
779
998
  this._DragPanelStartX = pEvent.clientX;
780
999
  this._DragPanelStartY = pEvent.clientY;
781
1000
  this._DragPanelDataStartX = tmpPanel.X;
782
1001
  this._DragPanelDataStartY = tmpPanel.Y;
783
-
784
- this._SVGElement.classList.add('panning');
785
1002
  }
786
1003
 
787
1004
  _onPanelDrag(pEvent)
@@ -800,8 +1017,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
800
1017
 
801
1018
  _endPanelDrag(pEvent)
802
1019
  {
803
- this._SVGElement.classList.remove('panning');
804
-
805
1020
  this._FlowView.marshalFromView();
806
1021
 
807
1022
  let tmpPanel = this._FlowView._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === this._DragPanelHash);
@@ -811,7 +1026,7 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
811
1026
  this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowView.flowData);
812
1027
  }
813
1028
 
814
- this._State = INTERACTION_STATES.IDLE;
1029
+ this._setState(INTERACTION_STATES.IDLE);
815
1030
  this._DragPanelHash = null;
816
1031
  }
817
1032
 
@@ -825,12 +1040,10 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
825
1040
  let tmpPanel = this._FlowView._FlowData.OpenPanels.find((pPanel) => pPanel.Hash === tmpPanelHash);
826
1041
  if (!tmpPanel) return;
827
1042
 
828
- this._State = INTERACTION_STATES.RESIZING_PANEL;
1043
+ this._setState(INTERACTION_STATES.RESIZING_PANEL);
829
1044
  this._ResizePanelHash = tmpPanelHash;
830
1045
  this._ResizeStartY = pEvent.clientY;
831
1046
  this._ResizePanelStartHeight = tmpPanel.Height;
832
-
833
- this._SVGElement.classList.add('panning');
834
1047
  }
835
1048
 
836
1049
  _onPanelResize(pEvent)
@@ -861,8 +1074,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
861
1074
 
862
1075
  _endPanelResize(pEvent)
863
1076
  {
864
- this._SVGElement.classList.remove('panning');
865
-
866
1077
  // Re-render to sync tethers
867
1078
  this._FlowView.renderFlow();
868
1079
  this._FlowView.marshalFromView();
@@ -872,7 +1083,7 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
872
1083
  this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowView.flowData);
873
1084
  }
874
1085
 
875
- this._State = INTERACTION_STATES.IDLE;
1086
+ this._setState(INTERACTION_STATES.IDLE);
876
1087
  this._ResizePanelHash = null;
877
1088
  }
878
1089
 
@@ -891,14 +1102,12 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
891
1102
  let tmpNode = this._FlowView.getNode(tmpNodeHash);
892
1103
  if (!tmpNode) return;
893
1104
 
894
- this._State = INTERACTION_STATES.RESIZING_NODE;
1105
+ this._setState(INTERACTION_STATES.RESIZING_NODE);
895
1106
  this._ResizeNodeHash = tmpNodeHash;
896
1107
  this._ResizeNodeStartX = pEvent.clientX;
897
1108
  this._ResizeNodeStartY = pEvent.clientY;
898
1109
  this._ResizeNodeStartWidth = tmpNode.Width || 180;
899
1110
  this._ResizeNodeStartHeight = tmpNode.Height || 80;
900
-
901
- this._SVGElement.classList.add('panning');
902
1111
  if (pEvent.stopPropagation) pEvent.stopPropagation();
903
1112
  }
904
1113
 
@@ -925,8 +1134,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
925
1134
 
926
1135
  _endNodeResize(pEvent)
927
1136
  {
928
- this._SVGElement.classList.remove('panning');
929
-
930
1137
  this._FlowView.renderFlow();
931
1138
  this._FlowView.marshalFromView();
932
1139
 
@@ -937,23 +1144,208 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
937
1144
  this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowView.flowData);
938
1145
  }
939
1146
 
940
- this._State = INTERACTION_STATES.IDLE;
1147
+ this._setState(INTERACTION_STATES.IDLE);
941
1148
  this._ResizeNodeHash = null;
942
1149
  }
943
1150
 
1151
+ // ---- Content-frame editing (drag the view-area box) ----
1152
+
1153
+ _startFrameResize(pEvent, pTarget)
1154
+ {
1155
+ if (!this._FlowView.options.EnableFrameEditing) return;
1156
+ let tmpFrame = this._FlowView._resolveFrame();
1157
+ if (!tmpFrame) return;
1158
+
1159
+ this._setState(INTERACTION_STATES.RESIZING_FRAME);
1160
+ this._FrameDragEdge = (pTarget && pTarget.getAttribute) ? pTarget.getAttribute('data-frame-edge') : null;
1161
+ this._FrameStart = { X: tmpFrame.X || 0, Y: tmpFrame.Y || 0, Width: tmpFrame.Width, Height: tmpFrame.Height };
1162
+ this._FrameStartPointerX = pEvent.clientX;
1163
+ this._FrameStartPointerY = pEvent.clientY;
1164
+ if (pEvent.stopPropagation) pEvent.stopPropagation();
1165
+ }
1166
+
1167
+ _startFrameMove(pEvent, pTarget)
1168
+ {
1169
+ if (!this._FlowView.options.EnableFrameEditing) return;
1170
+ let tmpFrame = this._FlowView._resolveFrame();
1171
+ if (!tmpFrame) return;
1172
+
1173
+ this._setState(INTERACTION_STATES.MOVING_FRAME);
1174
+ this._FrameStart = { X: tmpFrame.X || 0, Y: tmpFrame.Y || 0, Width: tmpFrame.Width, Height: tmpFrame.Height };
1175
+ this._FrameStartPointerX = pEvent.clientX;
1176
+ this._FrameStartPointerY = pEvent.clientY;
1177
+ if (pEvent.stopPropagation) pEvent.stopPropagation();
1178
+ }
1179
+
1180
+ _onFrameResize(pEvent)
1181
+ {
1182
+ if (!this._FrameStart) return;
1183
+ let tmpVS = this._FlowView.viewState;
1184
+ let tmpDX = (pEvent.clientX - this._FrameStartPointerX) / tmpVS.Zoom;
1185
+ let tmpDY = (pEvent.clientY - this._FrameStartPointerY) / tmpVS.Zoom;
1186
+ let tmpMin = this._FlowView.options.MinimumFrameSize || 40;
1187
+ this._applyFrameEdit(PictServiceFlowInteractionManager.computeFrameResize(this._FrameStart, this._FrameDragEdge, tmpDX, tmpDY, tmpMin));
1188
+ }
1189
+
1190
+ _onFrameMove(pEvent)
1191
+ {
1192
+ if (!this._FrameStart) return;
1193
+ let tmpVS = this._FlowView.viewState;
1194
+ let tmpDX = (pEvent.clientX - this._FrameStartPointerX) / tmpVS.Zoom;
1195
+ let tmpDY = (pEvent.clientY - this._FrameStartPointerY) / tmpVS.Zoom;
1196
+ this._applyFrameEdit(
1197
+ {
1198
+ X: Math.round(this._FrameStart.X + tmpDX),
1199
+ Y: Math.round(this._FrameStart.Y + tmpDY),
1200
+ Width: this._FrameStart.Width,
1201
+ Height: this._FrameStart.Height
1202
+ });
1203
+ }
1204
+
1205
+ _applyFrameEdit(pBox)
1206
+ {
1207
+ let tmpFrame = this._FlowView._resolveFrame();
1208
+ if (!tmpFrame) return;
1209
+ tmpFrame.X = pBox.X;
1210
+ tmpFrame.Y = pBox.Y;
1211
+ tmpFrame.Width = pBox.Width;
1212
+ tmpFrame.Height = pBox.Height;
1213
+ this._FlowView._applyFrame();
1214
+ }
1215
+
1216
+ _endFrameEdit(pEvent)
1217
+ {
1218
+ let tmpFrame = this._FlowView._resolveFrame();
1219
+ this._FlowView._applyFrame();
1220
+ if (typeof this._FlowView.marshalFromView === 'function') this._FlowView.marshalFromView();
1221
+ if (this._FlowView._EventHandlerProvider)
1222
+ {
1223
+ this._FlowView._EventHandlerProvider.fireEvent('onFrameChanged', tmpFrame);
1224
+ this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowView.flowData);
1225
+ }
1226
+ this._setState(INTERACTION_STATES.IDLE);
1227
+ this._FrameDragEdge = null;
1228
+ this._FrameStart = null;
1229
+ }
1230
+
1231
+ /**
1232
+ * Pure box-resize math for the content frame: given the starting box, which edge is being
1233
+ * dragged ('n'|'e'|'s'|'w'), and the pointer delta in content units, return the new box,
1234
+ * clamped to a minimum size with the opposite edge held fixed. Unit tested without a DOM.
1235
+ * @param {Object} pStart - { X, Y, Width, Height }
1236
+ * @param {string} pEdge
1237
+ * @param {number} pDX
1238
+ * @param {number} pDY
1239
+ * @param {number} [pMinSize]
1240
+ * @returns {{X:number, Y:number, Width:number, Height:number}}
1241
+ */
1242
+ static computeFrameResize(pStart, pEdge, pDX, pDY, pMinSize)
1243
+ {
1244
+ let tmpMin = (typeof pMinSize === 'number') ? pMinSize : 40;
1245
+ let tmpX = pStart.X;
1246
+ let tmpY = pStart.Y;
1247
+ let tmpW = pStart.Width;
1248
+ let tmpH = pStart.Height;
1249
+ switch (pEdge)
1250
+ {
1251
+ case 'n':
1252
+ {
1253
+ let tmpNewY = pStart.Y + pDY;
1254
+ let tmpNewH = pStart.Height - pDY;
1255
+ if (tmpNewH < tmpMin) { tmpNewY = pStart.Y + (pStart.Height - tmpMin); tmpNewH = tmpMin; }
1256
+ tmpY = Math.round(tmpNewY);
1257
+ tmpH = Math.round(tmpNewH);
1258
+ break;
1259
+ }
1260
+ case 's':
1261
+ tmpH = Math.round(Math.max(tmpMin, pStart.Height + pDY));
1262
+ break;
1263
+ case 'e':
1264
+ tmpW = Math.round(Math.max(tmpMin, pStart.Width + pDX));
1265
+ break;
1266
+ case 'w':
1267
+ {
1268
+ let tmpNewX = pStart.X + pDX;
1269
+ let tmpNewW = pStart.Width - pDX;
1270
+ if (tmpNewW < tmpMin) { tmpNewX = pStart.X + (pStart.Width - tmpMin); tmpNewW = tmpMin; }
1271
+ tmpX = Math.round(tmpNewX);
1272
+ tmpW = Math.round(tmpNewW);
1273
+ break;
1274
+ }
1275
+ }
1276
+ return { X: tmpX, Y: tmpY, Width: tmpW, Height: tmpH };
1277
+ }
1278
+
1279
+ // ---- Node Rotation ----
1280
+ // Drag the grip on the arm above the selected node (rendered when EnableNodeRotation is on) to set
1281
+ // the node's Rotation in degrees: the card's top points toward the pointer. Hold Shift to snap to 15
1282
+ // degrees. The properties-panel rotation control still works; this is just a second, inline way.
1283
+
1284
+ _startNodeRotate(pEvent, pTarget)
1285
+ {
1286
+ if (!this._FlowView.options.EnableNodeRotation) return;
1287
+ if (this._isReadOnly()) return;
1288
+
1289
+ let tmpNodeHash = this._getNodeHash(pTarget);
1290
+ if (!tmpNodeHash) return;
1291
+ if (!this._FlowView.getNode(tmpNodeHash)) return;
1292
+
1293
+ this._setState(INTERACTION_STATES.ROTATING_NODE);
1294
+ this._RotateNodeHash = tmpNodeHash;
1295
+ if (pEvent.stopPropagation) pEvent.stopPropagation();
1296
+ }
1297
+
1298
+ _onNodeRotate(pEvent)
1299
+ {
1300
+ if (!this._RotateNodeHash) return;
1301
+
1302
+ let tmpNode = this._FlowView.getNode(this._RotateNodeHash);
1303
+ if (!tmpNode) return;
1304
+
1305
+ let tmpCenterX = (tmpNode.X || 0) + (tmpNode.Width || 0) / 2;
1306
+ let tmpCenterY = (tmpNode.Y || 0) + (tmpNode.Height || 0) / 2;
1307
+
1308
+ // Pointer in content (SVG) space so pan/zoom do not skew the angle.
1309
+ let tmpPoint = this._FlowView.screenToSVGCoords(pEvent.clientX, pEvent.clientY);
1310
+ let tmpAngle = (Math.atan2(tmpPoint.y - tmpCenterY, tmpPoint.x - tmpCenterX) * 180 / Math.PI) + 90;
1311
+
1312
+ // Shift snaps to 15-degree increments.
1313
+ if (pEvent.shiftKey) { tmpAngle = Math.round(tmpAngle / 15) * 15; }
1314
+
1315
+ // Normalize to [0, 360).
1316
+ tmpAngle = ((Math.round(tmpAngle) % 360) + 360) % 360;
1317
+ tmpNode.Rotation = tmpAngle;
1318
+
1319
+ this._FlowView.renderFlow();
1320
+ }
1321
+
1322
+ _endNodeRotate(pEvent)
1323
+ {
1324
+ this._FlowView.renderFlow();
1325
+ this._FlowView.marshalFromView();
1326
+
1327
+ let tmpNode = this._FlowView.getNode(this._RotateNodeHash);
1328
+ if (tmpNode && this._FlowView._EventHandlerProvider)
1329
+ {
1330
+ this._FlowView._EventHandlerProvider.fireEvent('onNodeRotated', tmpNode);
1331
+ this._FlowView._EventHandlerProvider.fireEvent('onFlowChanged', this._FlowView.flowData);
1332
+ }
1333
+
1334
+ this._setState(INTERACTION_STATES.IDLE);
1335
+ this._RotateNodeHash = null;
1336
+ }
1337
+
944
1338
  // ---- Handle Dragging ----
945
1339
 
946
1340
  _startHandleDrag(pEvent, pConnectionHash, pPanelHash, pHandleType, pIsTether)
947
1341
  {
948
- this._State = INTERACTION_STATES.DRAGGING_HANDLE;
1342
+ this._setState(INTERACTION_STATES.DRAGGING_HANDLE);
949
1343
  this._DragHandleConnectionHash = pConnectionHash;
950
1344
  this._DragHandlePanelHash = pPanelHash;
951
1345
  this._DragHandleType = pHandleType;
952
1346
  this._DragHandleIsTether = pIsTether;
953
1347
  this._DragStartX = pEvent.clientX;
954
- this._DragStartY = pEvent.clientY;
955
- this._SVGElement.classList.add('panning');
956
- }
1348
+ this._DragStartY = pEvent.clientY; }
957
1349
 
958
1350
  _onHandleDrag(pEvent)
959
1351
  {
@@ -981,8 +1373,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
981
1373
 
982
1374
  _endHandleDrag(pEvent)
983
1375
  {
984
- this._SVGElement.classList.remove('panning');
985
-
986
1376
  this._FlowView.renderFlow();
987
1377
  this._FlowView.marshalFromView();
988
1378
 
@@ -1008,7 +1398,7 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
1008
1398
  }
1009
1399
  }
1010
1400
 
1011
- this._State = INTERACTION_STATES.IDLE;
1401
+ this._setState(INTERACTION_STATES.IDLE);
1012
1402
  this._DragHandleConnectionHash = null;
1013
1403
  this._DragHandlePanelHash = null;
1014
1404
  this._DragHandleType = null;
@@ -1163,17 +1553,16 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
1163
1553
 
1164
1554
  if (!tmpNodeHash || !tmpPortHash) return;
1165
1555
 
1166
- if (tmpPortDirection !== 'output')
1556
+ // A connection normally starts from an output port; undirected mode lets any port start one.
1557
+ if (!this._FlowView.options.EnableUndirectedConnections && tmpPortDirection !== 'output')
1167
1558
  {
1168
1559
  return;
1169
1560
  }
1170
1561
 
1171
- this._State = INTERACTION_STATES.CONNECTING;
1562
+ this._setState(INTERACTION_STATES.CONNECTING);
1172
1563
  this._ConnectSourceNodeHash = tmpNodeHash;
1173
1564
  this._ConnectSourcePortHash = tmpPortHash;
1174
1565
 
1175
- this._SVGElement.classList.add('connecting');
1176
-
1177
1566
  let tmpPortPos = this._FlowView.getPortPosition(tmpNodeHash, tmpPortHash);
1178
1567
  if (tmpPortPos)
1179
1568
  {
@@ -1208,8 +1597,6 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
1208
1597
  }
1209
1598
  this._ConnectDragLine = null;
1210
1599
 
1211
- this._SVGElement.classList.remove('connecting');
1212
-
1213
1600
  let tmpTarget = document.elementFromPoint(pEvent.clientX, pEvent.clientY);
1214
1601
  if (tmpTarget)
1215
1602
  {
@@ -1217,7 +1604,8 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
1217
1604
  let tmpTargetNodeHash = tmpTarget.getAttribute('data-node-hash');
1218
1605
  let tmpTargetPortDirection = tmpTarget.getAttribute('data-port-direction');
1219
1606
 
1220
- if (tmpTargetPortHash && tmpTargetNodeHash && tmpTargetPortDirection === 'input')
1607
+ // A connection normally completes on an input port; undirected mode lets any port receive it.
1608
+ if (tmpTargetPortHash && tmpTargetNodeHash && (this._FlowView.options.EnableUndirectedConnections || tmpTargetPortDirection === 'input'))
1221
1609
  {
1222
1610
  this._FlowView.addConnection(
1223
1611
  this._ConnectSourceNodeHash,
@@ -1228,7 +1616,7 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
1228
1616
  }
1229
1617
  }
1230
1618
 
1231
- this._State = INTERACTION_STATES.IDLE;
1619
+ this._setState(INTERACTION_STATES.IDLE);
1232
1620
  this._ConnectSourceNodeHash = null;
1233
1621
  this._ConnectSourcePortHash = null;
1234
1622
  }
@@ -1241,9 +1629,7 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
1241
1629
  }
1242
1630
  this._ConnectDragLine = null;
1243
1631
 
1244
- this._SVGElement.classList.remove('connecting');
1245
-
1246
- this._State = INTERACTION_STATES.IDLE;
1632
+ this._setState(INTERACTION_STATES.IDLE);
1247
1633
  this._ConnectSourceNodeHash = null;
1248
1634
  this._ConnectSourcePortHash = null;
1249
1635
  }
@@ -1254,13 +1640,11 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
1254
1640
  {
1255
1641
  this._FlowView.deselectAll();
1256
1642
 
1257
- this._State = INTERACTION_STATES.PANNING;
1643
+ this._setState(INTERACTION_STATES.PANNING);
1258
1644
  this._PanStartX = pEvent.clientX;
1259
1645
  this._PanStartY = pEvent.clientY;
1260
1646
  this._PanStartPanX = this._FlowView.viewState.PanX;
1261
1647
  this._PanStartPanY = this._FlowView.viewState.PanY;
1262
-
1263
- this._SVGElement.classList.add('panning');
1264
1648
  }
1265
1649
 
1266
1650
  _onPan(pEvent)
@@ -1275,9 +1659,7 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
1275
1659
  }
1276
1660
 
1277
1661
  _endPanning(pEvent)
1278
- {
1279
- this._SVGElement.classList.remove('panning');
1280
- this._State = INTERACTION_STATES.IDLE;
1662
+ { this._setState(INTERACTION_STATES.IDLE);
1281
1663
  }
1282
1664
 
1283
1665
  // ---- Marquee selection (multi-select) ----
@@ -1293,7 +1675,7 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
1293
1675
  this._MarqueeStartY = tmpStart.y;
1294
1676
  this._MarqueeCurrentX = tmpStart.x;
1295
1677
  this._MarqueeCurrentY = tmpStart.y;
1296
- this._State = INTERACTION_STATES.MARQUEE;
1678
+ this._setState(INTERACTION_STATES.MARQUEE);
1297
1679
 
1298
1680
  let tmpRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
1299
1681
  tmpRect.setAttribute('class', 'pict-flow-marquee');
@@ -1331,7 +1713,7 @@ class PictServiceFlowInteractionManager extends libFableServiceProviderBase
1331
1713
  this._MarqueeElement.parentNode.removeChild(this._MarqueeElement);
1332
1714
  }
1333
1715
  this._MarqueeElement = null;
1334
- this._State = INTERACTION_STATES.IDLE;
1716
+ this._setState(INTERACTION_STATES.IDLE);
1335
1717
 
1336
1718
  // Too small to be a deliberate drag: treat as a background click and clear the selection.
1337
1719
  if (tmpW < 4 && tmpH < 4)