w-flow-vue 1.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 (144) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc.js +55 -0
  4. package/.jsdoc +25 -0
  5. package/AGENT.md +223 -0
  6. package/LICENSE +21 -0
  7. package/README.md +37 -0
  8. package/SECURITY.md +5 -0
  9. package/babel.config.js +16 -0
  10. package/dist/w-flow-vue.umd.js +15 -0
  11. package/dist/w-flow-vue.umd.js.map +1 -0
  12. package/docs/components_WFlowVue.vue.html +1214 -0
  13. package/docs/examples/app.html +62 -0
  14. package/docs/examples/app.umd.js +20 -0
  15. package/docs/examples/app.umd.js.map +1 -0
  16. package/docs/examples/ex-AppBasic.html +440 -0
  17. package/docs/examples/ex-AppConnectivity.html +131 -0
  18. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  19. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  20. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  21. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  22. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  23. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  24. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  25. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  26. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  27. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
  28. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  29. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  30. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  31. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  32. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
  33. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  34. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  35. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  36. package/docs/global.html +1919 -0
  37. package/docs/index.html +84 -0
  38. package/docs/js_defaults.mjs.html +105 -0
  39. package/docs/js_edge-path.mjs.html +237 -0
  40. package/docs/js_geometry.mjs.html +298 -0
  41. package/docs/js_graph.mjs.html +103 -0
  42. package/docs/js_step-routing.mjs.html +346 -0
  43. package/docs/module-WFlowVue.html +2790 -0
  44. package/docs/scripts/collapse.js +39 -0
  45. package/docs/scripts/commonNav.js +28 -0
  46. package/docs/scripts/linenumber.js +25 -0
  47. package/docs/scripts/nav.js +12 -0
  48. package/docs/scripts/polyfill.js +4 -0
  49. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  50. package/docs/scripts/prettify/lang-css.js +2 -0
  51. package/docs/scripts/prettify/prettify.js +28 -0
  52. package/docs/scripts/search.js +99 -0
  53. package/docs/styles/jsdoc.css +776 -0
  54. package/docs/styles/prettify.css +80 -0
  55. package/jest.config.js +20 -0
  56. package/package.json +80 -0
  57. package/public/index.html +38 -0
  58. package/script.txt +22 -0
  59. package/src/App.vue +326 -0
  60. package/src/AppBasic.vue +125 -0
  61. package/src/AppConnectivity.vue +186 -0
  62. package/src/components/WFlowVue.vue +1142 -0
  63. package/src/components/canvas/BackgroundLayer.vue +78 -0
  64. package/src/components/canvas/FlowCanvas.vue +64 -0
  65. package/src/components/canvas/SelectionBox.vue +36 -0
  66. package/src/components/canvas/ViewportTransform.vue +35 -0
  67. package/src/components/edges/ConnectionLine.vue +65 -0
  68. package/src/components/edges/EdgeMarkerDefs.vue +76 -0
  69. package/src/components/edges/EdgeRenderer.vue +120 -0
  70. package/src/components/edges/EdgeWrapper.vue +379 -0
  71. package/src/components/nodes/DefaultNode.vue +276 -0
  72. package/src/components/nodes/Handle.vue +101 -0
  73. package/src/components/nodes/InputNode.vue +47 -0
  74. package/src/components/nodes/NodeBody.vue +103 -0
  75. package/src/components/nodes/NodeFace.vue +128 -0
  76. package/src/components/nodes/NodeRenderer.vue +95 -0
  77. package/src/components/nodes/NodeWrapper.vue +475 -0
  78. package/src/components/nodes/OutputNode.vue +47 -0
  79. package/src/components/ui/ConnSettingsForm.vue +158 -0
  80. package/src/components/ui/Controls.vue +83 -0
  81. package/src/components/ui/NodeSettingsForm.vue +185 -0
  82. package/src/js/defaults.mjs +33 -0
  83. package/src/js/edge-path.mjs +165 -0
  84. package/src/js/geometry.mjs +226 -0
  85. package/src/js/graph.mjs +31 -0
  86. package/src/js/step-routing.mjs +274 -0
  87. package/src/main.js +22 -0
  88. package/test/WFlowVue-features.test.mjs +760 -0
  89. package/test/WFlowVue.test.mjs +421 -0
  90. package/test/components-canvas.test.mjs +102 -0
  91. package/test/components-edge.test.mjs +147 -0
  92. package/test/components-node.test.mjs +174 -0
  93. package/test/components-ui.test.mjs +69 -0
  94. package/test/defaults.test.mjs +86 -0
  95. package/test/edge-path.test.mjs +102 -0
  96. package/test/generate-routing-snapshots.mjs +77 -0
  97. package/test/generate-visual-baselines.mjs +206 -0
  98. package/test/geometry.test.mjs +236 -0
  99. package/test/graph.test.mjs +72 -0
  100. package/test/jsons/routing-snapshots.json +24994 -0
  101. package/test/pics/_check2.png +0 -0
  102. package/test/pics/_check3.png +0 -0
  103. package/test/pics/_check4.png +0 -0
  104. package/test/pics/_check5.png +0 -0
  105. package/test/pics/_v1.png +0 -0
  106. package/test/pics/_v2.png +0 -0
  107. package/test/pics/_v3.png +0 -0
  108. package/test/pics/_v4.png +0 -0
  109. package/test/pics/_v5.png +0 -0
  110. package/test/pics/_v6.png +0 -0
  111. package/test/pics/_v7.png +0 -0
  112. package/test/pics/vb-edge-hovered.png +0 -0
  113. package/test/pics/vb-edges-normal.png +0 -0
  114. package/test/pics/vb-locked-edge-hovered.png +0 -0
  115. package/test/pics/vb-locked-node-hovered.png +0 -0
  116. package/test/pics/vb-locked-node-selected.png +0 -0
  117. package/test/pics/vb-locked-overview.png +0 -0
  118. package/test/pics/vb-node-1.png +0 -0
  119. package/test/pics/vb-node-10.png +0 -0
  120. package/test/pics/vb-node-11.png +0 -0
  121. package/test/pics/vb-node-12.png +0 -0
  122. package/test/pics/vb-node-2.png +0 -0
  123. package/test/pics/vb-node-3.png +0 -0
  124. package/test/pics/vb-node-4.png +0 -0
  125. package/test/pics/vb-node-5.png +0 -0
  126. package/test/pics/vb-node-6.png +0 -0
  127. package/test/pics/vb-node-7.png +0 -0
  128. package/test/pics/vb-node-8.png +0 -0
  129. package/test/pics/vb-node-9.png +0 -0
  130. package/test/pics/vb-node-hovered.png +0 -0
  131. package/test/pics/vb-node-selected.png +0 -0
  132. package/test/pics/vb-overview.png +0 -0
  133. package/test/step-routing-connectivity.test.mjs +78 -0
  134. package/test/step-routing.test.mjs +88 -0
  135. package/test/visual-regression.test.mjs +274 -0
  136. package/toolg/addVersion.mjs +4 -0
  137. package/toolg/cleanFolder.mjs +4 -0
  138. package/toolg/gDistApp.mjs +34 -0
  139. package/toolg/gDistRollupComps.mjs +22 -0
  140. package/toolg/gDocExams.mjs +47 -0
  141. package/toolg/gExtractHtml.mjs +179 -0
  142. package/toolg/modifyReadme.mjs +4 -0
  143. package/vue.config.js +9 -0
  144. package/vue2/344/271/213foreignObject/345/205/247/346/270/262/346/237/223/345/225/217/351/241/214/350/210/207/344/277/256/346/255/243.md +151 -0
@@ -0,0 +1,298 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>js/geometry.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-WFlowVue.html">WFlowVue</a></li></ul><h3>Global</h3><ul><li><a href="global.html#NODE_DEFAULTS">NODE_DEFAULTS</a></li><li><a href="global.html#clampPosition">clampPosition</a></li><li><a href="global.html#generateId">generateId</a></li><li><a href="global.html#getBezierPath">getBezierPath</a></li><li><a href="global.html#getControlOffset">getControlOffset</a></li><li><a href="global.html#getDiamondEdgePoint">getDiamondEdgePoint</a></li><li><a href="global.html#getEllipseEdgePoint">getEllipseEdgePoint</a></li><li><a href="global.html#getHandlePosition">getHandlePosition</a></li><li><a href="global.html#getOverlappingNodes">getOverlappingNodes</a></li><li><a href="global.html#getSmoothStepPath">getSmoothStepPath</a></li><li><a href="global.html#getStepPath">getStepPath</a></li><li><a href="global.html#getStraightPath">getStraightPath</a></li><li><a href="global.html#getTriangleEdgePoint">getTriangleEdgePoint</a></li><li><a href="global.html#isValidConnection">isValidConnection</a></li><li><a href="global.html#labelAtHalfLength">labelAtHalfLength</a></li><li><a href="global.html#lookupRoute">lookupRoute</a></li><li><a href="global.html#rectsOverlap">rectsOverlap</a></li><li><a href="global.html#segmentFallback">segmentFallback</a></li><li><a href="global.html#snapPosition">snapPosition</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">js/geometry.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>/**
49
+ * Calculate the absolute position of a handle on the canvas.
50
+ */
51
+ export function getHandlePosition(node, handlePosition, nodeInternals, handleType) {
52
+ const internals = nodeInternals || {}
53
+ const w = (internals.width) || node.width || 150
54
+ const h = (internals.height) || node.height || 40
55
+ const x = node.position.x
56
+ const y = node.position.y
57
+ const isDiamond = node.shape === 'diamond'
58
+ const isEllipse = node.shape === 'ellipse'
59
+ const ns = node.shape
60
+ const isTriangle = ns === 'triangle' || ns === 'triangle-right' || ns === 'triangle-down' || ns === 'triangle-left'
61
+
62
+ // Check if this is a default node with source and target on the same side
63
+ const sameSide = node.type === 'basic' &amp;&amp;
64
+ (node.toPosition || 'bottom') === (node.fromPosition || 'top')
65
+ let ratio = 0.5
66
+ if (sameSide &amp;&amp; handleType === 'target') ratio = 0.33
67
+ if (sameSide &amp;&amp; handleType === 'source') ratio = 0.67
68
+
69
+ // Diamond same-side: position along diamond edges
70
+ if (isDiamond &amp;&amp; sameSide) {
71
+ return getDiamondEdgePoint(x, y, w, h, handlePosition, ratio)
72
+ }
73
+
74
+ // Ellipse: position on the ellipse border
75
+ if (isEllipse) {
76
+ return getEllipseEdgePoint(x, y, w, h, handlePosition, ratio)
77
+ }
78
+
79
+ // Triangle: position on the triangle edges
80
+ if (isTriangle) {
81
+ return getTriangleEdgePoint(x, y, w, h, handlePosition, ratio, ns)
82
+ }
83
+
84
+ switch (handlePosition) {
85
+ case 'top': return { x: x + w * ratio, y }
86
+ case 'bottom': return { x: x + w * ratio, y: y + h }
87
+ case 'left': return { x, y: y + h * ratio }
88
+ case 'right': return { x: x + w, y: y + h * ratio }
89
+ default: return { x: x + w / 2, y: y + h }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Get a point on the diamond edge for same-side handles.
95
+ * Each "side" of the diamond is split into two edges meeting at the vertex.
96
+ * ratio &lt; 0.5: on the first edge, ratio >= 0.5: on the second edge.
97
+ */
98
+ function getDiamondEdgePoint(x, y, w, h, side, ratio) {
99
+ let halfW = w / 2
100
+ let halfH = h / 2
101
+
102
+ switch (side) {
103
+ case 'top':
104
+ if (ratio &lt;= 0.5) {
105
+ let t = ratio * 2
106
+ return { x: x + t * halfW, y: y + halfH - t * halfH }
107
+ }
108
+ else {
109
+ let t = (ratio - 0.5) * 2
110
+ return { x: x + halfW + t * halfW, y: y + t * halfH }
111
+ }
112
+ case 'bottom':
113
+ if (ratio &lt;= 0.5) {
114
+ let t = ratio * 2
115
+ return { x: x + t * halfW, y: y + halfH + t * halfH }
116
+ }
117
+ else {
118
+ let t = (ratio - 0.5) * 2
119
+ return { x: x + halfW + t * halfW, y: y + h - t * halfH }
120
+ }
121
+ case 'left':
122
+ if (ratio &lt;= 0.5) {
123
+ let t = ratio * 2
124
+ return { x: x + halfW - t * halfW, y: y + t * halfH }
125
+ }
126
+ else {
127
+ let t = (ratio - 0.5) * 2
128
+ return { x: x + t * halfW, y: y + halfH + t * halfH }
129
+ }
130
+ case 'right':
131
+ if (ratio &lt;= 0.5) {
132
+ let t = ratio * 2
133
+ return { x: x + halfW + t * halfW, y: y + t * halfH }
134
+ }
135
+ else {
136
+ let t = (ratio - 0.5) * 2
137
+ return { x: x + w - t * halfW, y: y + halfH + t * halfH }
138
+ }
139
+ default:
140
+ return { x: x + halfW, y: y + h }
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Get a point on the ellipse edge for handle positioning.
146
+ * Maps ratio (0..1) to a parametric angle based on the side.
147
+ */
148
+ function getEllipseEdgePoint(x, y, w, h, side, ratio) {
149
+ let cx = x + w / 2
150
+ let cy = y + h / 2
151
+ let rx = w / 2
152
+ let ry = h / 2
153
+ let angle
154
+
155
+ switch (side) {
156
+ case 'top':
157
+ angle = Math.PI * (1 - ratio); break
158
+ case 'bottom':
159
+ angle = Math.PI * (ratio - 1); break
160
+ case 'left':
161
+ angle = Math.PI * (0.5 + ratio); break
162
+ case 'right':
163
+ angle = Math.PI * (0.5 - ratio); break
164
+ default:
165
+ angle = 0
166
+ }
167
+
168
+ return {
169
+ x: cx + rx * Math.cos(angle),
170
+ y: cy - ry * Math.sin(angle)
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Get a point on the triangle edge for handle positioning.
176
+ * Supports 4 directions: triangle (up), triangle-right, triangle-down, triangle-left.
177
+ */
178
+ function getTriangleEdgePoint(x, y, w, h, side, ratio, shape) {
179
+ // Get absolute vertices based on direction
180
+ let apex, baseA, baseB, apexSide, baseSide, edgeA, edgeB
181
+ if (shape === 'triangle-right') {
182
+ apex = { x: x + w, y: y + h / 2 }; baseA = { x, y }; baseB = { x, y: y + h }
183
+ apexSide = 'right'; baseSide = 'left'; edgeA = 'top'; edgeB = 'bottom'
184
+ }
185
+ else if (shape === 'triangle-down') {
186
+ apex = { x: x + w / 2, y: y + h }; baseA = { x, y }; baseB = { x: x + w, y }
187
+ apexSide = 'bottom'; baseSide = 'top'; edgeA = 'left'; edgeB = 'right'
188
+ }
189
+ else if (shape === 'triangle-left') {
190
+ apex = { x, y: y + h / 2 }; baseA = { x: x + w, y }; baseB = { x: x + w, y: y + h }
191
+ apexSide = 'left'; baseSide = 'right'; edgeA = 'top'; edgeB = 'bottom'
192
+ }
193
+ else {
194
+ // triangle (up)
195
+ apex = { x: x + w / 2, y }; baseA = { x, y: y + h }; baseB = { x: x + w, y: y + h }
196
+ apexSide = 'top'; baseSide = 'bottom'; edgeA = 'left'; edgeB = 'right'
197
+ }
198
+
199
+ if (side === apexSide) {
200
+ if (ratio &lt;= 0.5) {
201
+ let t = ratio * 2
202
+ return { x: baseA.x + (apex.x - baseA.x) * t, y: baseA.y + (apex.y - baseA.y) * t }
203
+ }
204
+ else {
205
+ let t2 = (ratio - 0.5) * 2
206
+ return { x: apex.x + (baseB.x - apex.x) * t2, y: apex.y + (baseB.y - apex.y) * t2 }
207
+ }
208
+ }
209
+ if (side === baseSide) {
210
+ return { x: baseA.x + (baseB.x - baseA.x) * ratio, y: baseA.y + (baseB.y - baseA.y) * ratio }
211
+ }
212
+ if (side === edgeA) {
213
+ return { x: apex.x + (baseA.x - apex.x) * ratio, y: apex.y + (baseA.y - apex.y) * ratio }
214
+ }
215
+ if (side === edgeB) {
216
+ return { x: apex.x + (baseB.x - apex.x) * ratio, y: apex.y + (baseB.y - apex.y) * ratio }
217
+ }
218
+ return { x: x + w / 2, y: y + h / 2 }
219
+ }
220
+
221
+ /**
222
+ * Get all nodes that overlap with a given rectangle.
223
+ */
224
+ export function getOverlappingNodes(rect, nodes, nodeInternals) {
225
+ return nodes.filter(node => {
226
+ const internals = (nodeInternals &amp;&amp; nodeInternals[node.id]) || {}
227
+ const w = internals.width || node.width || 150
228
+ const h = internals.height || node.height || 40
229
+ const nodeRect = {
230
+ x: node.position.x,
231
+ y: node.position.y,
232
+ width: w,
233
+ height: h,
234
+ }
235
+ return rectsOverlap(rect, nodeRect)
236
+ })
237
+ }
238
+
239
+ /**
240
+ * Check if two rectangles overlap.
241
+ */
242
+ function rectsOverlap(a, b) {
243
+ return (
244
+ a.x &lt; b.x + b.width &amp;&amp;
245
+ a.x + a.width > b.x &amp;&amp;
246
+ a.y &lt; b.y + b.height &amp;&amp;
247
+ a.y + a.height > b.y
248
+ )
249
+ }
250
+
251
+ /**
252
+ * Clamp a position within a coordinate extent.
253
+ */
254
+ export function clampPosition(position, extent) {
255
+ if (!extent) return position
256
+ return {
257
+ x: Math.max(extent[0][0], Math.min(extent[1][0], position.x)),
258
+ y: Math.max(extent[0][1], Math.min(extent[1][1], position.y)),
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Snap a position to the nearest grid point.
264
+ * @param {{ x: number, y: number }} position
265
+ * @param {number|null} gridSize - Grid cell size (single number for both axes)
266
+ */
267
+ export function snapPosition(position, gridSize) {
268
+ if (!gridSize) return position
269
+ return {
270
+ x: Math.round(position.x / gridSize) * gridSize,
271
+ y: Math.round(position.y / gridSize) * gridSize,
272
+ }
273
+ }
274
+ </code></pre>
275
+ </article>
276
+ </section>
277
+
278
+
279
+
280
+
281
+
282
+
283
+ </div>
284
+
285
+ <br class="clear">
286
+
287
+ <footer>
288
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Apr 12 2026 22:18:33 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
289
+ </footer>
290
+
291
+ <script>prettyPrint();</script>
292
+ <script src="scripts/polyfill.js"></script>
293
+ <script src="scripts/linenumber.js"></script>
294
+
295
+
296
+
297
+ </body>
298
+ </html>
@@ -0,0 +1,103 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>js/graph.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-WFlowVue.html">WFlowVue</a></li></ul><h3>Global</h3><ul><li><a href="global.html#NODE_DEFAULTS">NODE_DEFAULTS</a></li><li><a href="global.html#clampPosition">clampPosition</a></li><li><a href="global.html#generateId">generateId</a></li><li><a href="global.html#getBezierPath">getBezierPath</a></li><li><a href="global.html#getControlOffset">getControlOffset</a></li><li><a href="global.html#getDiamondEdgePoint">getDiamondEdgePoint</a></li><li><a href="global.html#getEllipseEdgePoint">getEllipseEdgePoint</a></li><li><a href="global.html#getHandlePosition">getHandlePosition</a></li><li><a href="global.html#getOverlappingNodes">getOverlappingNodes</a></li><li><a href="global.html#getSmoothStepPath">getSmoothStepPath</a></li><li><a href="global.html#getStepPath">getStepPath</a></li><li><a href="global.html#getStraightPath">getStraightPath</a></li><li><a href="global.html#getTriangleEdgePoint">getTriangleEdgePoint</a></li><li><a href="global.html#isValidConnection">isValidConnection</a></li><li><a href="global.html#labelAtHalfLength">labelAtHalfLength</a></li><li><a href="global.html#lookupRoute">lookupRoute</a></li><li><a href="global.html#rectsOverlap">rectsOverlap</a></li><li><a href="global.html#segmentFallback">segmentFallback</a></li><li><a href="global.html#snapPosition">snapPosition</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">js/graph.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>/**
49
+ * Validate a potential connection.
50
+ */
51
+ export function isValidConnection(connection, nodes, conns, validator) {
52
+ if (!connection.from || !connection.to) return false
53
+
54
+ // Self-connection not allowed by default
55
+ if (connection.from === connection.to) return false
56
+
57
+ // Check duplicate (same from→to path)
58
+ const duplicate = conns.find(
59
+ e => e.from === connection.from &amp;&amp; e.to === connection.to
60
+ )
61
+ if (duplicate) return false
62
+
63
+ // Custom validator
64
+ if (validator &amp;&amp; !validator(connection)) return false
65
+
66
+ return true
67
+ }
68
+
69
+ let _idCounter = 0
70
+
71
+ /**
72
+ * Generate a unique ID.
73
+ */
74
+ export function generateId() {
75
+ _idCounter++
76
+ return `${Date.now()}-${_idCounter}-${Math.random().toString(36).slice(2, 7)}`
77
+ }
78
+
79
+ </code></pre>
80
+ </article>
81
+ </section>
82
+
83
+
84
+
85
+
86
+
87
+
88
+ </div>
89
+
90
+ <br class="clear">
91
+
92
+ <footer>
93
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Apr 12 2026 22:18:33 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
94
+ </footer>
95
+
96
+ <script>prettyPrint();</script>
97
+ <script src="scripts/polyfill.js"></script>
98
+ <script src="scripts/linenumber.js"></script>
99
+
100
+
101
+
102
+ </body>
103
+ </html>