w-flow-vue 1.0.21 → 1.0.23
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/README.md +1 -1
- package/dist/w-flow-vue.umd.js +3 -3
- package/dist/w-flow-vue.umd.js.map +1 -1
- package/docs/components_WFlowVue.vue.html +61 -6
- package/docs/components_ui_Controls.vue.html +1 -1
- package/docs/examples/app.html +1 -1
- package/docs/examples/app.umd.js +4 -4
- package/docs/examples/app.umd.js.map +1 -1
- package/docs/examples/ex-AppBasic.html +1 -1
- package/docs/examples/ex-AppConnectivity.html +1 -1
- package/docs/global.html +1 -1
- package/docs/index.html +1 -1
- package/docs/js_defaults.mjs.html +1 -1
- package/docs/js_edgePath.mjs.html +1 -1
- package/docs/js_fixSvgNs.mjs.html +1 -1
- package/docs/js_geometry.mjs.html +1 -1
- package/docs/js_graph.mjs.html +1 -1
- package/docs/js_stepRouting.mjs.html +1 -1
- package/docs/module-WFlowVue.html +1 -1
- package/package.json +1 -1
- package/src/components/WFlowVue.vue +60 -5
- package/src/components/nodes/NodeRenderer.vue +1 -0
- package/src/components/nodes/NodeWrapper.vue +81 -19
- package/test/pics/flow-E2E-011-node-settings-form.png +0 -0
- package/test/pics/flow-E2E-012-popup-exclusive.png +0 -0
- package/test/unit-WFlowVue-features.test.mjs +10 -8
- package/test/unit-WFlowVue.test.mjs +432 -421
- package/test/unit-components-canvas.test.mjs +104 -102
- package/test/unit-components-edge.test.mjs +157 -147
- package/test/unit-components-node.test.mjs +27 -5
- package/test/unit-geometry.test.mjs +248 -236
- package/test/unit-graph.test.mjs +72 -72
- package/test/unit-node-drag-gesture.test.mjs +254 -0
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
/>
|
|
44
44
|
|
|
45
45
|
<!-- w-flow-vue -->
|
|
46
|
-
<script src="https://cdn.jsdelivr.net/npm/w-flow-vue@1.0.
|
|
46
|
+
<script src="https://cdn.jsdelivr.net/npm/w-flow-vue@1.0.23/dist/w-flow-vue.umd.js"></script>
|
|
47
47
|
|
|
48
48
|
<!-- lodash -->
|
|
49
49
|
<script src="https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"></script>
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
/>
|
|
44
44
|
|
|
45
45
|
<!-- w-flow-vue -->
|
|
46
|
-
<script src="https://cdn.jsdelivr.net/npm/w-flow-vue@1.0.
|
|
46
|
+
<script src="https://cdn.jsdelivr.net/npm/w-flow-vue@1.0.23/dist/w-flow-vue.umd.js"></script>
|
|
47
47
|
|
|
48
48
|
<!-- lodash -->
|
|
49
49
|
<script src="https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"></script>
|
package/docs/global.html
CHANGED
|
@@ -2354,7 +2354,7 @@ Used when nodes are too close for OrthConnector (tooShort).</p></li></ul></dd>
|
|
|
2354
2354
|
<br class="clear">
|
|
2355
2355
|
|
|
2356
2356
|
<footer>
|
|
2357
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
2357
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 23 2026 15:03:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
2358
2358
|
</footer>
|
|
2359
2359
|
|
|
2360
2360
|
<script>prettyPrint();</script>
|
package/docs/index.html
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
<br class="clear">
|
|
72
72
|
|
|
73
73
|
<footer>
|
|
74
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
74
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 23 2026 15:03:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
75
75
|
</footer>
|
|
76
76
|
|
|
77
77
|
<script>prettyPrint();</script>
|
|
@@ -92,7 +92,7 @@ export const CONN_DEFAULTS = {
|
|
|
92
92
|
<br class="clear">
|
|
93
93
|
|
|
94
94
|
<footer>
|
|
95
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
95
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 23 2026 15:03:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
96
96
|
</footer>
|
|
97
97
|
|
|
98
98
|
<script>prettyPrint();</script>
|
|
@@ -342,7 +342,7 @@ function labelAtHalfLength(pts) {
|
|
|
342
342
|
<br class="clear">
|
|
343
343
|
|
|
344
344
|
<footer>
|
|
345
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
345
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 23 2026 15:03:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
346
346
|
</footer>
|
|
347
347
|
|
|
348
348
|
<script>prettyPrint();</script>
|
|
@@ -85,7 +85,7 @@ export default fixSvgNs
|
|
|
85
85
|
<br class="clear">
|
|
86
86
|
|
|
87
87
|
<footer>
|
|
88
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
88
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 23 2026 15:03:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
89
89
|
</footer>
|
|
90
90
|
|
|
91
91
|
<script>prettyPrint();</script>
|
|
@@ -285,7 +285,7 @@ export function snapPosition(position, gridSize) {
|
|
|
285
285
|
<br class="clear">
|
|
286
286
|
|
|
287
287
|
<footer>
|
|
288
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
288
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 23 2026 15:03:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
289
289
|
</footer>
|
|
290
290
|
|
|
291
291
|
<script>prettyPrint();</script>
|
package/docs/js_graph.mjs.html
CHANGED
|
@@ -96,7 +96,7 @@ export function generateId() {
|
|
|
96
96
|
<br class="clear">
|
|
97
97
|
|
|
98
98
|
<footer>
|
|
99
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
99
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 23 2026 15:03:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
100
100
|
</footer>
|
|
101
101
|
|
|
102
102
|
<script>prettyPrint();</script>
|
|
@@ -333,7 +333,7 @@ function segmentFallback(sx, sy, tx, ty, sObs, tObs) {
|
|
|
333
333
|
<br class="clear">
|
|
334
334
|
|
|
335
335
|
<footer>
|
|
336
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
336
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 23 2026 15:03:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
337
337
|
</footer>
|
|
338
338
|
|
|
339
339
|
<script>prettyPrint();</script>
|
|
@@ -4186,7 +4186,7 @@ Any value of the wrong type (or an empty string) falls back to the default liste
|
|
|
4186
4186
|
<br class="clear">
|
|
4187
4187
|
|
|
4188
4188
|
<footer>
|
|
4189
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
4189
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 23 2026 15:03:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
4190
4190
|
</footer>
|
|
4191
4191
|
|
|
4192
4192
|
<script>prettyPrint();</script>
|
package/package.json
CHANGED
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
:snap-grid-size="snapToGrid ? snapGridSize : null"
|
|
75
75
|
:settings-enabled="nodesSettingsEnabled"
|
|
76
76
|
:settings-excludes="nodesSettingsExcludes"
|
|
77
|
+
@drag-prepare="onNodeDragPrepare"
|
|
77
78
|
@drag-start="onNodeDragStart"
|
|
78
79
|
@node-click="onNodeClick"
|
|
79
80
|
@node-double-click="onNodeDoubleClick"
|
|
@@ -377,6 +378,7 @@ export default {
|
|
|
377
378
|
document.addEventListener('keyup', this.onKeyUp)
|
|
378
379
|
document.addEventListener('mousemove', this.onDocMouseMove)
|
|
379
380
|
document.addEventListener('mouseup', this.onDocMouseUp)
|
|
381
|
+
window.addEventListener('blur', this.onWindowBlur)
|
|
380
382
|
},
|
|
381
383
|
beforeDestroy() {
|
|
382
384
|
if (this._panAnimId) {
|
|
@@ -387,6 +389,7 @@ export default {
|
|
|
387
389
|
document.removeEventListener('keyup', this.onKeyUp)
|
|
388
390
|
document.removeEventListener('mousemove', this.onDocMouseMove)
|
|
389
391
|
document.removeEventListener('mouseup', this.onDocMouseUp)
|
|
392
|
+
window.removeEventListener('blur', this.onWindowBlur)
|
|
390
393
|
},
|
|
391
394
|
computed: {
|
|
392
395
|
widthInp() {
|
|
@@ -766,6 +769,8 @@ export default {
|
|
|
766
769
|
},
|
|
767
770
|
onCanvasWheel(event) {
|
|
768
771
|
if (!this.zoomOnScroll) return
|
|
772
|
+
//滾輪縮放亦為viewport寫入者, 先取消程式動畫避免二者互相覆寫
|
|
773
|
+
this.cancelViewportAnimation()
|
|
769
774
|
const delta = -event.deltaY * 0.001
|
|
770
775
|
const currentZoom = this.viewport.zoom
|
|
771
776
|
// fitView may set zoom below zoomMin; use the current level as the
|
|
@@ -789,6 +794,14 @@ export default {
|
|
|
789
794
|
|
|
790
795
|
// --- Document-level mouse ---
|
|
791
796
|
onDocMouseMove(event) {
|
|
797
|
+
//按鍵已放開卻仍處於進行中狀態: 代表收尾事件未送達(於瀏覽器視窗外放開/視窗失焦/手勢被原生拖曳接管),
|
|
798
|
+
//此時須主動走既有收尾路徑清除狀態; 只return不清狀態無效, 旗標續留為真下次移動仍會誤判
|
|
799
|
+
if ((event.buttons & 1) === 0) {
|
|
800
|
+
if (this.isPanning || this.isDraggingNode || this.isConnecting || this.isSelecting) {
|
|
801
|
+
this.onDocMouseUp(event)
|
|
802
|
+
}
|
|
803
|
+
return
|
|
804
|
+
}
|
|
792
805
|
if (this.isPanning) {
|
|
793
806
|
this.doPan(event)
|
|
794
807
|
}
|
|
@@ -802,6 +815,19 @@ export default {
|
|
|
802
815
|
this.doSelection(event)
|
|
803
816
|
}
|
|
804
817
|
},
|
|
818
|
+
onWindowBlur(event) {
|
|
819
|
+
//視窗失焦後不會再收到mouseup與keyup, 於此統一收尾避免狀態黏住
|
|
820
|
+
if (this.isPanning) {
|
|
821
|
+
this.endPan()
|
|
822
|
+
}
|
|
823
|
+
//節點拖曳同樣須收尾: 否則isDraggingNode殘留為true, 回到視窗後在觸發mousemove守衛前
|
|
824
|
+
//點擊其他節點, 該次node-click會被onNodeClick之拖曳守衛以過期狀態誤擋
|
|
825
|
+
//event為blur事件(非MouseEvent), endDrag不讀取座標僅原樣轉交node-drag-stop, 故不偽造mouseup
|
|
826
|
+
if (this.isDraggingNode) {
|
|
827
|
+
this.endDrag(event)
|
|
828
|
+
}
|
|
829
|
+
this.keysPressed = {}
|
|
830
|
+
},
|
|
805
831
|
onDocMouseUp(event) {
|
|
806
832
|
if (this.isPanning) {
|
|
807
833
|
this.endPan()
|
|
@@ -819,6 +845,8 @@ export default {
|
|
|
819
845
|
|
|
820
846
|
// --- Pan ---
|
|
821
847
|
startPan(event) {
|
|
848
|
+
//手動平移優先於程式動畫, 否則二者同時寫viewport而互相覆寫
|
|
849
|
+
this.cancelViewportAnimation()
|
|
822
850
|
this.isPanning = true
|
|
823
851
|
this.panStartPos = { x: event.clientX, y: event.clientY }
|
|
824
852
|
},
|
|
@@ -846,16 +874,20 @@ export default {
|
|
|
846
874
|
},
|
|
847
875
|
|
|
848
876
|
// --- Node drag ---
|
|
849
|
-
onNodeDragStart
|
|
877
|
+
//於節點mousedown當下先行選取(拖曳本身延後至跨越位移門檻才由onNodeDragStart啟動)。
|
|
878
|
+
//守衛與選取條件皆與修正前之onNodeDragStart一致, 故選取時機不因拖曳延後而改變。
|
|
879
|
+
onNodeDragPrepare({ node }) {
|
|
850
880
|
if (this.locked || !this.nodesDraggable) return
|
|
851
|
-
this.isDraggingNode = true
|
|
852
|
-
this.draggingNodeId = node.id
|
|
853
|
-
this.dragStartPos = { x: event.clientX, y: event.clientY }
|
|
854
|
-
|
|
855
881
|
if (this.selectNodesOnDrag && !this.isMultiSelectPressed) {
|
|
856
882
|
this.setSelectedNodes([node.id])
|
|
857
883
|
this.setSelectedConns([])
|
|
858
884
|
}
|
|
885
|
+
},
|
|
886
|
+
onNodeDragStart({ node, event, moveEvent }) {
|
|
887
|
+
if (this.locked || !this.nodesDraggable) return
|
|
888
|
+
this.isDraggingNode = true
|
|
889
|
+
this.draggingNodeId = node.id
|
|
890
|
+
this.dragStartPos = { x: event.clientX, y: event.clientY }
|
|
859
891
|
|
|
860
892
|
// Cache start positions for drag
|
|
861
893
|
const starts = {}
|
|
@@ -878,6 +910,12 @@ export default {
|
|
|
878
910
|
}
|
|
879
911
|
|
|
880
912
|
this.$emit('node-drag-start', { node, event })
|
|
913
|
+
|
|
914
|
+
//立即套用跨門檻的這一次mousemove: document層之onDocMouseMove於本輪事件已先看過
|
|
915
|
+
//(當時isDraggingNode仍為false而返回), 不補這一次則跨門檻後立刻放開將完全沒有位移
|
|
916
|
+
if (moveEvent && this.isDraggingNode) {
|
|
917
|
+
this.doDrag(moveEvent)
|
|
918
|
+
}
|
|
881
919
|
},
|
|
882
920
|
doDrag(event) {
|
|
883
921
|
const zoom = this.viewport.zoom
|
|
@@ -1014,6 +1052,10 @@ export default {
|
|
|
1014
1052
|
|
|
1015
1053
|
// --- Selection ---
|
|
1016
1054
|
onNodeClick({ node, event }) {
|
|
1055
|
+
//本次手勢已被接受為拖曳者不再視為點擊: NodeWrapper之@mouseup綁在節點元素上,
|
|
1056
|
+
//必先於document層之onDocMouseUp→endDrag觸發, 故此刻isDraggingNode仍為true即代表拖曳成立。
|
|
1057
|
+
//(涵蓋「跨門檻拖出後又移回原點放開」之情形——該情形最終位移為0, 僅靠距離判準無法辨識)
|
|
1058
|
+
if (this.isDraggingNode) return
|
|
1017
1059
|
if (!this.elementsSelectable) return
|
|
1018
1060
|
if (this.isMultiSelectPressed) {
|
|
1019
1061
|
const idx = this.selectedNodes.indexOf(node.id)
|
|
@@ -1252,6 +1294,19 @@ export default {
|
|
|
1252
1294
|
emitViewportChange() {
|
|
1253
1295
|
this.$emit('viewport-change', { ...this.viewport })
|
|
1254
1296
|
},
|
|
1297
|
+
getViewport() {
|
|
1298
|
+
//即時視口: viewport-change僅於手勢結束才發出, 平移途中呼叫端需要當下值時走此方法
|
|
1299
|
+
//(呼叫端若以viewport-change快取之值為基準回寫setViewport, 會於平移途中以過期值覆寫使用者正在進行的平移)
|
|
1300
|
+
let vp = this.viewport
|
|
1301
|
+
return { x: vp.x, y: vp.y, zoom: vp.zoom }
|
|
1302
|
+
},
|
|
1303
|
+
cancelViewportAnimation() {
|
|
1304
|
+
//取消進行中之視口動畫(panToNode), 否則動畫與手動手勢會同時寫入同一個viewport
|
|
1305
|
+
if (this._panAnimId) {
|
|
1306
|
+
cancelAnimationFrame(this._panAnimId)
|
|
1307
|
+
this._panAnimId = null
|
|
1308
|
+
}
|
|
1309
|
+
},
|
|
1255
1310
|
emitSelectionChange() {
|
|
1256
1311
|
this.$emit('selection-change', this.getSelectedElements())
|
|
1257
1312
|
},
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
:snap-grid-size="snapGridSize"
|
|
22
22
|
:settings-enabled="settingsEnabled"
|
|
23
23
|
:settings-excludes="settingsExcludes"
|
|
24
|
+
@drag-prepare="$emit('drag-prepare', $event)"
|
|
24
25
|
@drag-start="$emit('drag-start', $event)"
|
|
25
26
|
@node-click="$emit('node-click', $event)"
|
|
26
27
|
@node-double-click="$emit('node-double-click', $event)"
|
|
@@ -208,6 +208,12 @@ export default {
|
|
|
208
208
|
updated() {
|
|
209
209
|
this.reportDimensions()
|
|
210
210
|
},
|
|
211
|
+
beforeDestroy() {
|
|
212
|
+
//拖曳/縮放進行中被銷毀時, 掛在document上的監聽器與插入document.head之全域游標樣式
|
|
213
|
+
//都不會自行移除, 於此一併收尾
|
|
214
|
+
this.endMouseGesture(false)
|
|
215
|
+
this.endResizeGesture(false)
|
|
216
|
+
},
|
|
211
217
|
methods: {
|
|
212
218
|
reportDimensions() {
|
|
213
219
|
if (!this.$el) return
|
|
@@ -218,13 +224,57 @@ export default {
|
|
|
218
224
|
this.cachedH = h
|
|
219
225
|
this.$emit('dimensions', { nodeId: this.node.id, width: w, height: h })
|
|
220
226
|
},
|
|
227
|
+
//收掉本次mousedown手勢之document監聽與暫存態; restorePopup=false供beforeDestroy使用(元件將銷毀不需再排程還原)
|
|
228
|
+
endMouseGesture(restorePopup = true) {
|
|
229
|
+
const g = this._mouseGesture
|
|
230
|
+
if (!g) return
|
|
231
|
+
this._mouseGesture = null
|
|
232
|
+
this._mouseDownPos = null
|
|
233
|
+
document.removeEventListener('mousemove', g.onDragMove)
|
|
234
|
+
document.removeEventListener('mouseup', g.onDragEnd)
|
|
235
|
+
window.removeEventListener('blur', g.onDragEnd)
|
|
236
|
+
if (restorePopup && !this.infoPopupEditable) {
|
|
237
|
+
setTimeout(() => {
|
|
238
|
+
this.infoPopupEditable = true
|
|
239
|
+
}, 0)
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
//收掉本次resize手勢之document監聽與插入document.head之全域游標樣式;
|
|
243
|
+
//回傳是否確實收到一個進行中之手勢(供onMouseUp判斷該不該再發node-resize-end)
|
|
244
|
+
//why: 全域樣式為`* { cursor: X !important; }`, 若因元件銷毀或視窗外放開而未移除,
|
|
245
|
+
// 整頁每個元素都會卡在該游標直到重新整理(已於jsdom確定性重現: 銷毀後樣式仍留在head)
|
|
246
|
+
endResizeGesture(restorePopup = true) {
|
|
247
|
+
const g = this._resizeGesture
|
|
248
|
+
if (!g) return false
|
|
249
|
+
this._resizeGesture = null
|
|
250
|
+
document.removeEventListener('mousemove', g.onMouseMove)
|
|
251
|
+
document.removeEventListener('mouseup', g.onMouseUp)
|
|
252
|
+
window.removeEventListener('blur', g.onMouseUp)
|
|
253
|
+
if (g.cursorStyle && g.cursorStyle.parentNode) {
|
|
254
|
+
g.cursorStyle.parentNode.removeChild(g.cursorStyle)
|
|
255
|
+
}
|
|
256
|
+
if (restorePopup) {
|
|
257
|
+
setTimeout(() => {
|
|
258
|
+
this.infoPopupEditable = true
|
|
259
|
+
}, 0)
|
|
260
|
+
}
|
|
261
|
+
return true
|
|
262
|
+
},
|
|
221
263
|
onMouseDown(event) {
|
|
264
|
+
//新手勢開始前先收掉上一次殘留者(如上次mouseup落在視窗外未送達document), 避免監聽器疊加
|
|
265
|
+
this.endMouseGesture()
|
|
222
266
|
//點設定齒輪(及其popup錨區)不啟動節點拖曳/點擊: 齒輪刻意不用@mousedown.stop(stopPropagation會擋掉window層WPopup互斥協調),
|
|
223
267
|
//故於此明確排除, 使點齒輪只開設定popup、不移動節點座標(避免尚未變更設定就先改到座標→誤觸發變更儲存)
|
|
224
268
|
if (event.target.closest && event.target.closest('.vue-flow__node-settings-anchor')) {
|
|
225
269
|
this._mouseDownPos = null
|
|
226
270
|
return
|
|
227
271
|
}
|
|
272
|
+
//非主鍵(右鍵/中鍵)不啟動拖曳與點擊: 判準對齊畫布平移之onCanvasMouseDown(event.button === 0),
|
|
273
|
+
//使同套件內「按下開始拖動」採同一套判準; contextmenu走@contextmenu.stop另一條路徑不受影響
|
|
274
|
+
if (event.button !== 0) {
|
|
275
|
+
this._mouseDownPos = null
|
|
276
|
+
return
|
|
277
|
+
}
|
|
228
278
|
this._mouseDownPos = { x: event.clientX, y: event.clientY }
|
|
229
279
|
if (!this.draggable) return
|
|
230
280
|
if (this.node.dragHandle) {
|
|
@@ -232,26 +282,40 @@ export default {
|
|
|
232
282
|
if (!handle) return
|
|
233
283
|
}
|
|
234
284
|
this.infoPopupShow = false
|
|
235
|
-
|
|
285
|
+
//選取仍於mousedown完成(與修正前一致): 拖曳延後但選取不可延後,
|
|
286
|
+
//否則按住節點未移動時, 原本立即出現的選取回饋會拖到mouseup才出現
|
|
287
|
+
this.$emit('drag-prepare', { node: this.node, event })
|
|
236
288
|
const startX = event.clientX
|
|
237
289
|
const startY = event.clientY
|
|
238
|
-
const
|
|
290
|
+
const gesture = { crossed: false, onDragMove: null, onDragEnd: null }
|
|
291
|
+
gesture.onDragMove = (e) => {
|
|
292
|
+
//mouseup落在視窗外時不會送達document, 監聽器會殘留;
|
|
293
|
+
//此處確認主鍵仍按著, 否則於無按鍵狀態下跨門檻會誤啟動拖曳(幽靈拖曳)
|
|
294
|
+
if ((e.buttons & 1) === 0) {
|
|
295
|
+
this.endMouseGesture()
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
if (gesture.crossed) return
|
|
239
299
|
if (Math.abs(e.clientX - startX) > 2 || Math.abs(e.clientY - startY) > 2) {
|
|
300
|
+
gesture.crossed = true
|
|
240
301
|
this.infoPopupEditable = false
|
|
241
|
-
|
|
302
|
+
//拖曳延後至跨越位移門檻才啟動
|
|
303
|
+
//why: 若於mousedown即emit drag-start, 無位移之純點擊於mouseup仍會走endDrag→回寫座標→emit update:nodes,
|
|
304
|
+
// 宿主收到未變更之全量節點而誤判有未儲存變更
|
|
305
|
+
//event傳原始mousedown事件, 使WFlowVue之dragStartPos取按下當下座標(改傳本次mousemove會少算門檻位移);
|
|
306
|
+
//moveEvent供WFlowVue於啟動當下立即補跑一次doDrag, 否則跨門檻後立刻放開會完全沒有位移
|
|
307
|
+
this.$emit('drag-start', { node: this.node, event, moveEvent: e })
|
|
242
308
|
}
|
|
309
|
+
//刻意不移除mousemove: 後續仍需靠它做buttons清理, crossed旗標已保證不會重複啟動
|
|
243
310
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
document.removeEventListener('mouseup', onDragEnd)
|
|
247
|
-
if (!this.infoPopupEditable) {
|
|
248
|
-
setTimeout(() => {
|
|
249
|
-
this.infoPopupEditable = true
|
|
250
|
-
}, 0)
|
|
251
|
-
}
|
|
311
|
+
gesture.onDragEnd = () => {
|
|
312
|
+
this.endMouseGesture()
|
|
252
313
|
}
|
|
253
|
-
|
|
254
|
-
document.addEventListener('
|
|
314
|
+
this._mouseGesture = gesture
|
|
315
|
+
document.addEventListener('mousemove', gesture.onDragMove)
|
|
316
|
+
document.addEventListener('mouseup', gesture.onDragEnd)
|
|
317
|
+
//視窗失焦後收不到mouseup, 本地監聽與popup態同樣需收尾(WFlowVue之onWindowBlur只收父層狀態)
|
|
318
|
+
window.addEventListener('blur', gesture.onDragEnd)
|
|
255
319
|
},
|
|
256
320
|
onMouseUp(event) {
|
|
257
321
|
if (!this._mouseDownPos) return
|
|
@@ -376,12 +440,7 @@ export default {
|
|
|
376
440
|
let lastY = startPosY
|
|
377
441
|
|
|
378
442
|
const onMouseUp = () => {
|
|
379
|
-
|
|
380
|
-
document.removeEventListener('mouseup', onMouseUp)
|
|
381
|
-
document.head.removeChild(cursorStyle)
|
|
382
|
-
setTimeout(() => {
|
|
383
|
-
this.infoPopupEditable = true
|
|
384
|
-
}, 0)
|
|
443
|
+
if (!this.endResizeGesture()) return
|
|
385
444
|
this.$emit('node-resize-end', {
|
|
386
445
|
nodeId: this.node.id,
|
|
387
446
|
width: lastW,
|
|
@@ -391,8 +450,11 @@ export default {
|
|
|
391
450
|
})
|
|
392
451
|
}
|
|
393
452
|
|
|
453
|
+
this._resizeGesture = { onMouseMove, onMouseUp, cursorStyle }
|
|
394
454
|
document.addEventListener('mousemove', onMouseMove)
|
|
395
455
|
document.addEventListener('mouseup', onMouseUp)
|
|
456
|
+
//視窗失焦後收不到mouseup, 全域游標樣式與監聽器同樣需收尾
|
|
457
|
+
window.addEventListener('blur', onMouseUp)
|
|
396
458
|
},
|
|
397
459
|
},
|
|
398
460
|
}
|
|
Binary file
|
|
Binary file
|
|
@@ -122,10 +122,12 @@ describe('ConnSettingsForm', () => {
|
|
|
122
122
|
})
|
|
123
123
|
|
|
124
124
|
// 3. Node Resize
|
|
125
|
+
//縮放ghost已由獨立之 resizeOverlay 改為併入 dragPositions 之 per-key 機制(WFlowVue.onNodeResize),
|
|
126
|
+
//斷言意圖不變: 縮放中ghost帶有該次尺寸/座標, 結束或被鎖定時不留ghost
|
|
125
127
|
describe('Node Resize', () => {
|
|
126
128
|
test('onNodeResize sets overlay', () => {
|
|
127
129
|
const w = createWrapper(); w.vm.onNodeResize({ nodeId: '1', width: 200, height: 80, x: 50, y: 50 })
|
|
128
|
-
expect(w.vm.
|
|
130
|
+
expect(w.vm.dragPositions['1']).toEqual({ x: 50, y: 50, width: 200, height: 80 }); w.destroy()
|
|
129
131
|
})
|
|
130
132
|
test('onNodeResizeEnd updates node', () => {
|
|
131
133
|
const w = createWrapper()
|
|
@@ -134,12 +136,12 @@ describe('Node Resize', () => {
|
|
|
134
136
|
const n = w.vm.nodeById('1')
|
|
135
137
|
expect(n.width).toBe(200); expect(n.height).toBe(80)
|
|
136
138
|
expect(n.position.x).toBe(60); expect(n.position.y).toBe(70)
|
|
137
|
-
expect(w.vm.
|
|
139
|
+
expect(w.vm.dragPositions['1']).toBeNull(); w.destroy()
|
|
138
140
|
})
|
|
139
141
|
test('blocked when locked', () => {
|
|
140
142
|
const w = createWrapper(); w.vm.toggleInteractive()
|
|
141
143
|
w.vm.onNodeResize({ nodeId: '1', width: 200, height: 80, x: 50, y: 50 })
|
|
142
|
-
expect(w.vm.
|
|
144
|
+
expect(w.vm.dragPositions['1']).toBeFalsy(); w.destroy()
|
|
143
145
|
})
|
|
144
146
|
test('emits nodes-change', () => {
|
|
145
147
|
const w = createWrapper()
|
|
@@ -350,8 +352,8 @@ describe('Snap-to-Grid integration', () => {
|
|
|
350
352
|
const w = createWrapper({ snapToGrid: false })
|
|
351
353
|
// Directly call onNodeResize with non-round values
|
|
352
354
|
w.vm.onNodeResize({ nodeId: '1', width: 137, height: 63, x: 51, y: 49 })
|
|
353
|
-
expect(w.vm.
|
|
354
|
-
expect(w.vm.
|
|
355
|
+
expect(w.vm.dragPositions['1'].width).toBe(137)
|
|
356
|
+
expect(w.vm.dragPositions['1'].height).toBe(63)
|
|
355
357
|
w.destroy()
|
|
356
358
|
})
|
|
357
359
|
test('snapGridSize passed to NodeRenderer', () => {
|
|
@@ -434,7 +436,7 @@ describe('onDocMouseMove dispatching', () => {
|
|
|
434
436
|
const w = createWrapper()
|
|
435
437
|
w.vm.viewport = { x: 0, y: 0, zoom: 1 }
|
|
436
438
|
w.vm.startPan({ clientX: 100, clientY: 100 })
|
|
437
|
-
w.vm.onDocMouseMove({ clientX: 150, clientY: 120 })
|
|
439
|
+
w.vm.onDocMouseMove({ clientX: 150, clientY: 120, buttons: 1 })
|
|
438
440
|
expect(w.vm.viewport.x).toBe(50)
|
|
439
441
|
expect(w.vm.viewport.y).toBe(20)
|
|
440
442
|
w.destroy()
|
|
@@ -443,7 +445,7 @@ describe('onDocMouseMove dispatching', () => {
|
|
|
443
445
|
const w = createWrapper()
|
|
444
446
|
w.vm.$refs.canvas = { getContainerRect: () => ({ left: 0, top: 0, width: 800, height: 600 }) }
|
|
445
447
|
w.vm.startSelection({ clientX: 100, clientY: 100 })
|
|
446
|
-
w.vm.onDocMouseMove({ clientX: 200, clientY: 200 })
|
|
448
|
+
w.vm.onDocMouseMove({ clientX: 200, clientY: 200, buttons: 1 })
|
|
447
449
|
expect(w.vm.selectionBox.width).toBe(100)
|
|
448
450
|
expect(w.vm.selectionBox.height).toBe(100)
|
|
449
451
|
w.destroy()
|
|
@@ -451,7 +453,7 @@ describe('onDocMouseMove dispatching', () => {
|
|
|
451
453
|
test('does nothing when idle', () => {
|
|
452
454
|
const w = createWrapper()
|
|
453
455
|
w.vm.viewport = { x: 0, y: 0, zoom: 1 }
|
|
454
|
-
w.vm.onDocMouseMove({ clientX: 200, clientY: 200 })
|
|
456
|
+
w.vm.onDocMouseMove({ clientX: 200, clientY: 200, buttons: 1 })
|
|
455
457
|
expect(w.vm.viewport.x).toBe(0)
|
|
456
458
|
w.destroy()
|
|
457
459
|
})
|