@nyaruka/temba-components 0.141.0 → 0.142.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 (206) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/static/svg/index.svg +1 -1
  3. package/dist/temba-components.js +859 -656
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/Icons.js +3 -1
  6. package/out-tsc/src/Icons.js.map +1 -1
  7. package/out-tsc/src/display/Button.js +2 -2
  8. package/out-tsc/src/display/Button.js.map +1 -1
  9. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  10. package/out-tsc/src/flow/CanvasMenu.js +24 -1
  11. package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
  12. package/out-tsc/src/flow/CanvasNode.js +7 -2
  13. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  14. package/out-tsc/src/flow/Editor.js +665 -67
  15. package/out-tsc/src/flow/Editor.js.map +1 -1
  16. package/out-tsc/src/flow/NodeEditor.js +8 -5
  17. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  18. package/out-tsc/src/flow/Plumber.js +40 -28
  19. package/out-tsc/src/flow/Plumber.js.map +1 -1
  20. package/out-tsc/src/flow/actions/send_msg.js +2 -1
  21. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  22. package/out-tsc/src/flow/nodes/wait_for_response.js +1 -1
  23. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  24. package/out-tsc/src/flow/reflow.js +393 -0
  25. package/out-tsc/src/flow/reflow.js.map +1 -0
  26. package/out-tsc/src/flow/types.js.map +1 -1
  27. package/out-tsc/src/flow/utils.js +18 -3
  28. package/out-tsc/src/flow/utils.js.map +1 -1
  29. package/out-tsc/src/form/Compose.js +5 -0
  30. package/out-tsc/src/form/Compose.js.map +1 -1
  31. package/out-tsc/src/form/FieldRenderer.js +1 -3
  32. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  33. package/out-tsc/src/layout/Dialog.js +2 -0
  34. package/out-tsc/src/layout/Dialog.js.map +1 -1
  35. package/out-tsc/src/list/SortableList.js +39 -19
  36. package/out-tsc/src/list/SortableList.js.map +1 -1
  37. package/out-tsc/src/live/ContactChat.js +10 -1
  38. package/out-tsc/src/live/ContactChat.js.map +1 -1
  39. package/out-tsc/src/version.js +9 -0
  40. package/out-tsc/src/version.js.map +1 -0
  41. package/out-tsc/test/temba-canvas-menu.test.js +44 -0
  42. package/out-tsc/test/temba-canvas-menu.test.js.map +1 -1
  43. package/out-tsc/test/temba-contact-chat.test.js +12 -0
  44. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  45. package/out-tsc/test/temba-flow-collision.test.js +25 -0
  46. package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
  47. package/out-tsc/test/temba-flow-editor-zoom.test.js +491 -0
  48. package/out-tsc/test/temba-flow-editor-zoom.test.js.map +1 -0
  49. package/out-tsc/test/temba-flow-editor.test.js +164 -1
  50. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  51. package/out-tsc/test/temba-flow-node-drag.test.js +123 -0
  52. package/out-tsc/test/temba-flow-node-drag.test.js.map +1 -1
  53. package/out-tsc/test/temba-flow-plumber.test.js +31 -0
  54. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  55. package/out-tsc/test/temba-flow-reflow.test.js +472 -0
  56. package/out-tsc/test/temba-flow-reflow.test.js.map +1 -0
  57. package/out-tsc/test/temba-sortable-list.test.js +93 -0
  58. package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
  59. package/package.json +1 -1
  60. package/rollup.components.mjs +7 -1
  61. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  62. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  63. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  64. package/screenshots/truth/actions/add_contact_urn/editor/expression-facebook.png +0 -0
  65. package/screenshots/truth/actions/add_contact_urn/editor/expression-phone.png +0 -0
  66. package/screenshots/truth/actions/add_contact_urn/editor/facebook-id.png +0 -0
  67. package/screenshots/truth/actions/add_contact_urn/editor/instagram-handle.png +0 -0
  68. package/screenshots/truth/actions/add_contact_urn/editor/line-id.png +0 -0
  69. package/screenshots/truth/actions/add_contact_urn/editor/phone-number.png +0 -0
  70. package/screenshots/truth/actions/add_contact_urn/editor/telegram-id.png +0 -0
  71. package/screenshots/truth/actions/add_contact_urn/editor/viber-id.png +0 -0
  72. package/screenshots/truth/actions/add_contact_urn/editor/wechat-id.png +0 -0
  73. package/screenshots/truth/actions/add_contact_urn/editor/whatsapp.png +0 -0
  74. package/screenshots/truth/actions/enter_flow/editor/basic-flow.png +0 -0
  75. package/screenshots/truth/actions/enter_flow/editor/long-flow-name.png +0 -0
  76. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  77. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  78. package/screenshots/truth/actions/say_msg/editor/multiline-text.png +0 -0
  79. package/screenshots/truth/actions/say_msg/editor/simple-text.png +0 -0
  80. package/screenshots/truth/actions/say_msg/editor/text-with-audio-url.png +0 -0
  81. package/screenshots/truth/actions/send_broadcast/editor/contacts-only.png +0 -0
  82. package/screenshots/truth/actions/send_broadcast/editor/groups-and-contacts.png +0 -0
  83. package/screenshots/truth/actions/send_broadcast/editor/groups-only.png +0 -0
  84. package/screenshots/truth/actions/send_broadcast/editor/many-groups.png +0 -0
  85. package/screenshots/truth/actions/send_broadcast/editor/multiline-text.png +0 -0
  86. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  87. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  88. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  89. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  90. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  91. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  92. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  93. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  94. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  95. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  96. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  97. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  98. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  99. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  100. package/screenshots/truth/actions/set_contact_channel/editor/sms-channel.png +0 -0
  101. package/screenshots/truth/actions/set_contact_channel/editor/whatsapp-channel.png +0 -0
  102. package/screenshots/truth/actions/set_contact_field/editor/clear-value.png +0 -0
  103. package/screenshots/truth/actions/set_contact_field/editor/set-value.png +0 -0
  104. package/screenshots/truth/actions/set_contact_language/editor/english.png +0 -0
  105. package/screenshots/truth/actions/set_contact_language/editor/french.png +0 -0
  106. package/screenshots/truth/actions/set_contact_status/editor/active.png +0 -0
  107. package/screenshots/truth/actions/set_contact_status/editor/archived.png +0 -0
  108. package/screenshots/truth/actions/set_contact_status/editor/blocked.png +0 -0
  109. package/screenshots/truth/actions/set_run_result/editor/expression-value.png +0 -0
  110. package/screenshots/truth/actions/set_run_result/editor/with-category.png +0 -0
  111. package/screenshots/truth/actions/start_session/editor/contact-query.png +0 -0
  112. package/screenshots/truth/actions/start_session/editor/contacts-only.png +0 -0
  113. package/screenshots/truth/actions/start_session/editor/create-contact.png +0 -0
  114. package/screenshots/truth/actions/start_session/editor/groups-and-contacts.png +0 -0
  115. package/screenshots/truth/actions/start_session/editor/groups-only.png +0 -0
  116. package/screenshots/truth/actions/start_session/editor/many-recipients.png +0 -0
  117. package/screenshots/truth/list/fields-dragging.png +0 -0
  118. package/screenshots/truth/list/sortable-dragging.png +0 -0
  119. package/screenshots/truth/modax/simple.png +0 -0
  120. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  121. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  122. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  123. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  124. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  125. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  126. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  127. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  128. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  129. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  130. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  131. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  132. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  133. package/screenshots/truth/nodes/wait_for_dial/editor/dial-with-limits.png +0 -0
  134. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  135. package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
  136. package/screenshots/truth/nodes/wait_for_menu/editor/menu-with-digits.png +0 -0
  137. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  138. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  139. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  140. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  141. package/src/Icons.ts +3 -1
  142. package/src/display/Button.ts +2 -2
  143. package/src/display/FloatingTab.ts +1 -1
  144. package/src/flow/CanvasMenu.ts +28 -3
  145. package/src/flow/CanvasNode.ts +7 -2
  146. package/src/flow/Editor.ts +769 -76
  147. package/src/flow/NodeEditor.ts +8 -4
  148. package/src/flow/Plumber.ts +65 -35
  149. package/src/flow/actions/send_msg.ts +2 -1
  150. package/src/flow/nodes/wait_for_response.ts +1 -1
  151. package/src/flow/reflow.ts +534 -0
  152. package/src/flow/types.ts +1 -0
  153. package/src/flow/utils.ts +19 -3
  154. package/src/form/Compose.ts +5 -0
  155. package/src/form/FieldRenderer.ts +1 -3
  156. package/src/layout/Dialog.ts +2 -0
  157. package/src/list/SortableList.ts +40 -19
  158. package/src/live/ContactChat.ts +10 -1
  159. package/src/store/flow-definition.d.ts +1 -0
  160. package/src/version.ts +10 -0
  161. package/static/svg/index.svg +1 -1
  162. package/static/svg/work/traced/expand-06.svg +1 -0
  163. package/static/svg/work/used/expand-06.svg +3 -0
  164. package/test/temba-canvas-menu.test.ts +55 -0
  165. package/test/temba-contact-chat.test.ts +17 -0
  166. package/test/temba-flow-collision.test.ts +31 -0
  167. package/test/temba-flow-editor-zoom.test.ts +583 -0
  168. package/test/temba-flow-editor.test.ts +211 -1
  169. package/test/temba-flow-node-drag.test.ts +171 -0
  170. package/test/temba-flow-plumber.test.ts +38 -0
  171. package/test/temba-flow-reflow.test.ts +703 -0
  172. package/test/temba-sortable-list.test.ts +120 -0
  173. package/web-dev-server.config.mjs +5 -1
  174. package/web-test-runner.config.mjs +4 -1
  175. package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
  176. package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
  177. package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
  178. package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
  179. package/screenshots/truth/actions/call_llm/render/information-extraction.png +0 -0
  180. package/screenshots/truth/actions/call_llm/render/sentiment-analysis.png +0 -0
  181. package/screenshots/truth/actions/call_llm/render/summarization.png +0 -0
  182. package/screenshots/truth/actions/call_llm/render/translation-task.png +0 -0
  183. package/screenshots/truth/actions/send_broadcast/editor/with-attachments.png +0 -0
  184. package/screenshots/truth/actions/send_broadcast/render/with-attachments.png +0 -0
  185. package/screenshots/truth/compose/attachments-with-failures.png +0 -0
  186. package/screenshots/truth/compose/attachments-with-files-and-failures.png +0 -0
  187. package/screenshots/truth/contacts/tickets-assignment.png +0 -0
  188. package/screenshots/truth/contacts/tickets.png +0 -0
  189. package/screenshots/truth/flow/editor-basic.png +0 -0
  190. package/screenshots/truth/formfield/markdown-errors.png +0 -0
  191. package/screenshots/truth/formfield/no-errors.png +0 -0
  192. package/screenshots/truth/formfield/plain-text-errors.png +0 -0
  193. package/screenshots/truth/formfield/widget-only-markdown-errors.png +0 -0
  194. package/screenshots/truth/omnibox/selected.png +0 -0
  195. package/screenshots/truth/select/enabled-multi-selection.png +0 -0
  196. package/screenshots/truth/select/endpoint-initial-value-updated.png +0 -0
  197. package/screenshots/truth/select/endpoint-initial-value.png +0 -0
  198. package/screenshots/truth/select/initial-value.png +0 -0
  199. package/screenshots/truth/select/multi-reorder-final.png +0 -0
  200. package/screenshots/truth/select/multi-reorder-initial.png +0 -0
  201. package/screenshots/truth/select/selected-multi-test.png +0 -0
  202. package/screenshots/truth/select/value-initial.png +0 -0
  203. package/screenshots/truth/wait-for-response/rules-editor.png +0 -0
  204. package/screenshots/truth/wait-for-response/timeout-editor-unchecked.png +0 -0
  205. package/screenshots/truth/wait-for-response/timeout-editor.png +0 -0
  206. package/screenshots/truth/webchat/connecting-state.png +0 -0
@@ -253,5 +253,128 @@ describe('temba-flow-node drag and drop functionality', () => {
253
253
  document.removeEventListener('mousemove', handleMouseMove);
254
254
  document.removeEventListener('mouseup', handleMouseUp);
255
255
  });
256
+ describe('auto-scroll during drag', () => {
257
+ const AUTO_SCROLL_EDGE_ZONE = 100;
258
+ const AUTO_SCROLL_MAX_SPEED = 15;
259
+ function calculateScrollSpeed(mousePos, edgeStart, edgeEnd) {
260
+ const dx = 0;
261
+ const dy = 0;
262
+ // Left/top edge
263
+ const distFromStart = mousePos - edgeStart;
264
+ if (distFromStart >= 0 && distFromStart < AUTO_SCROLL_EDGE_ZONE) {
265
+ const ratio = 1 - distFromStart / AUTO_SCROLL_EDGE_ZONE;
266
+ return { dx: -(ratio * AUTO_SCROLL_MAX_SPEED), dy: 0 };
267
+ }
268
+ // Right/bottom edge
269
+ const distFromEnd = edgeEnd - mousePos;
270
+ if (distFromEnd >= 0 && distFromEnd < AUTO_SCROLL_EDGE_ZONE) {
271
+ const ratio = 1 - distFromEnd / AUTO_SCROLL_EDGE_ZONE;
272
+ return { dx: ratio * AUTO_SCROLL_MAX_SPEED, dy: 0 };
273
+ }
274
+ return { dx, dy };
275
+ }
276
+ it('should calculate scroll speed based on distance to edge', () => {
277
+ const edgeStart = 0;
278
+ const edgeEnd = 800;
279
+ // At the very left edge (distance = 0), speed should be max
280
+ let result = calculateScrollSpeed(0, edgeStart, edgeEnd);
281
+ assert.equal(result.dx, -AUTO_SCROLL_MAX_SPEED);
282
+ // At the edge zone boundary, speed should be 0
283
+ result = calculateScrollSpeed(AUTO_SCROLL_EDGE_ZONE, edgeStart, edgeEnd);
284
+ assert.equal(result.dx, 0);
285
+ // Halfway into the zone, speed should be half of max
286
+ result = calculateScrollSpeed(AUTO_SCROLL_EDGE_ZONE / 2, edgeStart, edgeEnd);
287
+ assert.closeTo(result.dx, -AUTO_SCROLL_MAX_SPEED / 2, 0.01);
288
+ // At the very right edge (distance = 0), speed should be max positive
289
+ result = calculateScrollSpeed(800, edgeStart, edgeEnd);
290
+ assert.equal(result.dx, AUTO_SCROLL_MAX_SPEED);
291
+ // Halfway into the right edge zone
292
+ result = calculateScrollSpeed(edgeEnd - AUTO_SCROLL_EDGE_ZONE / 2, edgeStart, edgeEnd);
293
+ assert.closeTo(result.dx, AUTO_SCROLL_MAX_SPEED / 2, 0.01);
294
+ // In the middle of the viewport, no scrolling
295
+ result = calculateScrollSpeed(400, edgeStart, edgeEnd);
296
+ assert.equal(result.dx, 0);
297
+ });
298
+ it('should not scroll when mouse is outside the viewport', () => {
299
+ const edgeStart = 0;
300
+ const edgeEnd = 800;
301
+ // Mouse is to the left of the viewport
302
+ const result = calculateScrollSpeed(-10, edgeStart, edgeEnd);
303
+ assert.equal(result.dx, 0);
304
+ });
305
+ it('should account for scroll delta in drag position calculation', () => {
306
+ // Simulate the formula: deltaX = (clientX - startX) + autoScrollDeltaX
307
+ const dragStartX = 400;
308
+ const currentClientX = 450;
309
+ const autoScrollDeltaX = 200;
310
+ const deltaX = currentClientX - dragStartX + autoScrollDeltaX;
311
+ // Without auto-scroll, delta would be 50. With 200px of scroll, it's 250.
312
+ assert.equal(deltaX, 250);
313
+ const originalLeft = 100;
314
+ const newLeft = originalLeft + deltaX;
315
+ assert.equal(newLeft, 350);
316
+ });
317
+ it('should accumulate scroll deltas correctly over multiple frames', () => {
318
+ let autoScrollDeltaX = 0;
319
+ let autoScrollDeltaY = 0;
320
+ // Simulate several frames of scrolling
321
+ const scrollIncrements = [
322
+ { dx: 5, dy: 3 },
323
+ { dx: 8, dy: 6 },
324
+ { dx: 10, dy: 9 },
325
+ { dx: 0, dy: 12 } // only vertical scrolling
326
+ ];
327
+ scrollIncrements.forEach(({ dx, dy }) => {
328
+ autoScrollDeltaX += dx;
329
+ autoScrollDeltaY += dy;
330
+ });
331
+ assert.equal(autoScrollDeltaX, 23);
332
+ assert.equal(autoScrollDeltaY, 30);
333
+ });
334
+ it('should clamp scroll delta when at scroll boundaries', () => {
335
+ // Simulate a scroll container at its left boundary
336
+ let scrollLeft = 0;
337
+ const requestedDx = -10;
338
+ const beforeScrollLeft = scrollLeft;
339
+ scrollLeft = Math.max(0, scrollLeft + requestedDx);
340
+ const actualDx = scrollLeft - beforeScrollLeft;
341
+ // At the boundary, actual delta should be 0
342
+ assert.equal(actualDx, 0);
343
+ // Simulate a scroll container not at boundary
344
+ scrollLeft = 50;
345
+ const beforeScrollLeft2 = scrollLeft;
346
+ scrollLeft = Math.max(0, scrollLeft + requestedDx);
347
+ const actualDx2 = scrollLeft - beforeScrollLeft2;
348
+ // Should have scrolled the full amount
349
+ assert.equal(actualDx2, -10);
350
+ });
351
+ it('should reset scroll deltas after drag ends', () => {
352
+ let autoScrollDeltaX = 150;
353
+ let autoScrollDeltaY = 200;
354
+ // Simulate drag end reset
355
+ autoScrollDeltaX = 0;
356
+ autoScrollDeltaY = 0;
357
+ assert.equal(autoScrollDeltaX, 0);
358
+ assert.equal(autoScrollDeltaY, 0);
359
+ });
360
+ it('should handle simultaneous horizontal and vertical auto-scroll', () => {
361
+ // Simulate mouse in the bottom-right corner of the viewport
362
+ const edgeEnd = 800;
363
+ const mousePos = 780; // 20px from the right/bottom edge
364
+ const distFromEnd = edgeEnd - mousePos;
365
+ assert.isTrue(distFromEnd < AUTO_SCROLL_EDGE_ZONE);
366
+ const ratio = 1 - distFromEnd / AUTO_SCROLL_EDGE_ZONE;
367
+ const scrollSpeed = ratio * AUTO_SCROLL_MAX_SPEED;
368
+ // Both axes should get the same speed when equidistant from edges
369
+ assert.isAbove(scrollSpeed, 0);
370
+ // Apply to both axes
371
+ let autoScrollDeltaX = 0;
372
+ let autoScrollDeltaY = 0;
373
+ autoScrollDeltaX += scrollSpeed;
374
+ autoScrollDeltaY += scrollSpeed;
375
+ assert.equal(autoScrollDeltaX, autoScrollDeltaY);
376
+ assert.isAbove(autoScrollDeltaX, 0);
377
+ });
378
+ });
256
379
  });
257
380
  //# sourceMappingURL=temba-flow-node-drag.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"temba-flow-node-drag.test.js","sourceRoot":"","sources":["../../test/temba-flow-node-drag.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { fixture, assert } from '@open-wc/testing';\nimport { html } from 'lit';\n\ndescribe('temba-flow-node drag and drop functionality', () => {\n it('should add drag styling and event listeners to elements', async () => {\n // Create a simple div to test our drag functionality\n const testElement = await fixture(html`\n <div\n class=\"node\"\n style=\"position: absolute; left: 100px; top: 100px; width: 200px; height: 100px; background: white; border: 1px solid #ccc; cursor: move;\"\n >\n Test Node\n <div class=\"exit\" style=\"padding: 5px; background: red;\">Exit</div>\n </div>\n `);\n\n // Test that the node has the correct cursor style\n const computedStyle = window.getComputedStyle(testElement);\n assert.equal(computedStyle.cursor, 'move');\n\n // Test drag event simulation\n let dragStarted = false;\n let dragEnded = false;\n let newPosition = { left: 0, top: 0 };\n\n // Simulate our drag implementation\n let isDragging = false;\n let dragStartPos = { x: 0, y: 0 };\n let nodeStartPos = { left: 100, top: 100 };\n\n const handleMouseDown = (event: MouseEvent) => {\n const target = event.target as HTMLElement;\n if (target.classList.contains('exit') || target.closest('.exit')) {\n return;\n }\n\n isDragging = true;\n dragStarted = true;\n dragStartPos = { x: event.clientX, y: event.clientY };\n nodeStartPos = { left: 100, top: 100 };\n event.preventDefault();\n event.stopPropagation();\n };\n\n const handleMouseMove = (event: MouseEvent) => {\n if (!isDragging) return;\n\n const deltaX = event.clientX - dragStartPos.x;\n const deltaY = event.clientY - dragStartPos.y;\n\n const newLeft = nodeStartPos.left + deltaX;\n const newTop = nodeStartPos.top + deltaY;\n\n // Update position\n (testElement as HTMLElement).style.left = `${newLeft}px`;\n (testElement as HTMLElement).style.top = `${newTop}px`;\n };\n\n const handleMouseUp = (event: MouseEvent) => {\n if (!isDragging) return;\n\n isDragging = false;\n dragEnded = true;\n\n const deltaX = event.clientX - dragStartPos.x;\n const deltaY = event.clientY - dragStartPos.y;\n\n newPosition = {\n left: nodeStartPos.left + deltaX,\n top: nodeStartPos.top + deltaY\n };\n };\n\n // Add event listeners\n testElement.addEventListener('mousedown', handleMouseDown);\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n\n // Test drag from node (should work)\n const mouseDownEvent = new MouseEvent('mousedown', {\n clientX: 150,\n clientY: 150,\n bubbles: true\n });\n testElement.dispatchEvent(mouseDownEvent);\n\n assert.isTrue(dragStarted, 'Drag should start when clicking on node');\n\n const mouseMoveEvent = new MouseEvent('mousemove', {\n clientX: 200,\n clientY: 200,\n bubbles: true\n });\n document.dispatchEvent(mouseMoveEvent);\n\n const mouseUpEvent = new MouseEvent('mouseup', {\n clientX: 200,\n clientY: 200,\n bubbles: true\n });\n document.dispatchEvent(mouseUpEvent);\n\n assert.isTrue(dragEnded, 'Drag should end on mouse up');\n assert.equal(\n newPosition.left,\n 150,\n 'New left position should be calculated correctly'\n );\n assert.equal(\n newPosition.top,\n 150,\n 'New top position should be calculated correctly'\n );\n\n // Test that position was visually updated\n assert.equal((testElement as HTMLElement).style.left, '150px');\n assert.equal((testElement as HTMLElement).style.top, '150px');\n\n // Reset for next test\n dragStarted = false;\n dragEnded = false;\n\n // Test drag from exit element (should not work)\n const exitElement = testElement.querySelector('.exit') as HTMLElement;\n const exitMouseDownEvent = new MouseEvent('mousedown', {\n clientX: 150,\n clientY: 150,\n bubbles: true\n });\n exitElement.dispatchEvent(exitMouseDownEvent);\n\n assert.isFalse(\n dragStarted,\n 'Drag should not start when clicking on exit element'\n );\n\n // Clean up\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n });\n\n it('should snap node positions to 20px grid during drag and drop', async () => {\n // Create a test node element\n const testElement = await fixture(html`\n <div\n class=\"node\"\n style=\"position: absolute; left: 100px; top: 100px; width: 200px; height: 100px; background: white; border: 1px solid #ccc; cursor: move;\"\n >\n Test Node for Grid Snapping\n </div>\n `);\n\n // Helper function to snap values to 20px grid (same as implementation)\n const snapToGrid = (value: number): number => {\n return Math.round(value / 20) * 20;\n };\n\n // Simulate drag implementation with grid snapping\n let isDragging = false;\n let dragStartPos = { x: 0, y: 0 };\n let nodeStartPos = { left: 100, top: 100 };\n let finalPosition = { left: 0, top: 0 };\n\n const handleMouseDown = (event: MouseEvent) => {\n isDragging = true;\n dragStartPos = { x: event.clientX, y: event.clientY };\n // Get current position from element style\n const currentLeft =\n parseInt((testElement as HTMLElement).style.left) || 0;\n const currentTop = parseInt((testElement as HTMLElement).style.top) || 0;\n nodeStartPos = { left: currentLeft, top: currentTop };\n event.preventDefault();\n event.stopPropagation();\n };\n\n const handleMouseMove = (event: MouseEvent) => {\n if (!isDragging) return;\n\n const deltaX = event.clientX - dragStartPos.x;\n const deltaY = event.clientY - dragStartPos.y;\n\n const newLeft = nodeStartPos.left + deltaX;\n const newTop = nodeStartPos.top + deltaY;\n\n // Snap to 20px grid\n const snappedLeft = snapToGrid(newLeft);\n const snappedTop = snapToGrid(newTop);\n\n // Update position with snapped values\n (testElement as HTMLElement).style.left = `${snappedLeft}px`;\n (testElement as HTMLElement).style.top = `${snappedTop}px`;\n };\n\n const handleMouseUp = (event: MouseEvent) => {\n if (!isDragging) return;\n isDragging = false;\n\n const deltaX = event.clientX - dragStartPos.x;\n const deltaY = event.clientY - dragStartPos.y;\n\n const newLeft = nodeStartPos.left + deltaX;\n const newTop = nodeStartPos.top + deltaY;\n\n // Snap final position to grid\n finalPosition = {\n left: snapToGrid(newLeft),\n top: snapToGrid(newTop)\n };\n };\n\n // Add event listeners\n testElement.addEventListener('mousedown', handleMouseDown);\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n\n // Test Case 1: Drag to position that should snap to grid\n // Starting at (100, 100), drag by (33, 27) pixels\n // Should result in (133, 127) -> snapped to (140, 120)\n const mouseDownEvent1 = new MouseEvent('mousedown', {\n clientX: 150,\n clientY: 150,\n bubbles: true\n });\n testElement.dispatchEvent(mouseDownEvent1);\n\n const mouseMoveEvent1 = new MouseEvent('mousemove', {\n clientX: 183, // 150 + 33\n clientY: 177, // 150 + 27\n bubbles: true\n });\n document.dispatchEvent(mouseMoveEvent1);\n\n // Check that position is snapped during drag\n assert.equal((testElement as HTMLElement).style.left, '140px');\n assert.equal((testElement as HTMLElement).style.top, '120px');\n\n const mouseUpEvent1 = new MouseEvent('mouseup', {\n clientX: 183,\n clientY: 177,\n bubbles: true\n });\n document.dispatchEvent(mouseUpEvent1);\n\n // Check final snapped position\n assert.equal(\n finalPosition.left,\n 140,\n 'Final left position should be snapped to 140px'\n );\n assert.equal(\n finalPosition.top,\n 120,\n 'Final top position should be snapped to 120px'\n );\n\n // Test Case 2: Test different snap scenarios\n // Reset position\n nodeStartPos = { left: 60, top: 80 };\n (testElement as HTMLElement).style.left = '60px';\n (testElement as HTMLElement).style.top = '80px';\n\n const mouseDownEvent2 = new MouseEvent('mousedown', {\n clientX: 100,\n clientY: 100,\n bubbles: true\n });\n testElement.dispatchEvent(mouseDownEvent2);\n\n // Drag by (15, 25) -> should snap to nearest 20px grid\n // (60 + 15, 80 + 25) = (75, 105) -> snapped to (80, 100)\n const mouseMoveEvent2 = new MouseEvent('mousemove', {\n clientX: 115, // 100 + 15\n clientY: 125, // 100 + 25\n bubbles: true\n });\n document.dispatchEvent(mouseMoveEvent2);\n\n assert.equal((testElement as HTMLElement).style.left, '80px');\n assert.equal((testElement as HTMLElement).style.top, '100px');\n\n const mouseUpEvent2 = new MouseEvent('mouseup', {\n clientX: 115,\n clientY: 125,\n bubbles: true\n });\n document.dispatchEvent(mouseUpEvent2);\n\n assert.equal(\n finalPosition.left,\n 80,\n 'Final left position should be snapped to 80px'\n );\n assert.equal(\n finalPosition.top,\n 100,\n 'Final top position should be snapped to 100px'\n );\n\n // Test Case 3: Test exact grid positions remain unchanged\n nodeStartPos = { left: 120, top: 160 }; // Already on grid\n (testElement as HTMLElement).style.left = '120px';\n (testElement as HTMLElement).style.top = '160px';\n\n const mouseDownEvent3 = new MouseEvent('mousedown', {\n clientX: 200,\n clientY: 200,\n bubbles: true\n });\n testElement.dispatchEvent(mouseDownEvent3);\n\n // Small movement that should stay on same grid position\n const mouseMoveEvent3 = new MouseEvent('mousemove', {\n clientX: 202, // +2 pixels\n clientY: 203, // +3 pixels\n bubbles: true\n });\n document.dispatchEvent(mouseMoveEvent3);\n\n // (120 + 2, 160 + 3) = (122, 163) -> snapped to (120, 160)\n assert.equal((testElement as HTMLElement).style.left, '120px');\n assert.equal((testElement as HTMLElement).style.top, '160px');\n\n const mouseUpEvent3 = new MouseEvent('mouseup', {\n clientX: 202,\n clientY: 203,\n bubbles: true\n });\n document.dispatchEvent(mouseUpEvent3);\n\n assert.equal(finalPosition.left, 120, 'Position should remain on grid');\n assert.equal(finalPosition.top, 160, 'Position should remain on grid');\n\n // Clean up\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n });\n});\n"]}
1
+ {"version":3,"file":"temba-flow-node-drag.test.js","sourceRoot":"","sources":["../../test/temba-flow-node-drag.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { fixture, assert } from '@open-wc/testing';\nimport { html } from 'lit';\n\ndescribe('temba-flow-node drag and drop functionality', () => {\n it('should add drag styling and event listeners to elements', async () => {\n // Create a simple div to test our drag functionality\n const testElement = await fixture(html`\n <div\n class=\"node\"\n style=\"position: absolute; left: 100px; top: 100px; width: 200px; height: 100px; background: white; border: 1px solid #ccc; cursor: move;\"\n >\n Test Node\n <div class=\"exit\" style=\"padding: 5px; background: red;\">Exit</div>\n </div>\n `);\n\n // Test that the node has the correct cursor style\n const computedStyle = window.getComputedStyle(testElement);\n assert.equal(computedStyle.cursor, 'move');\n\n // Test drag event simulation\n let dragStarted = false;\n let dragEnded = false;\n let newPosition = { left: 0, top: 0 };\n\n // Simulate our drag implementation\n let isDragging = false;\n let dragStartPos = { x: 0, y: 0 };\n let nodeStartPos = { left: 100, top: 100 };\n\n const handleMouseDown = (event: MouseEvent) => {\n const target = event.target as HTMLElement;\n if (target.classList.contains('exit') || target.closest('.exit')) {\n return;\n }\n\n isDragging = true;\n dragStarted = true;\n dragStartPos = { x: event.clientX, y: event.clientY };\n nodeStartPos = { left: 100, top: 100 };\n event.preventDefault();\n event.stopPropagation();\n };\n\n const handleMouseMove = (event: MouseEvent) => {\n if (!isDragging) return;\n\n const deltaX = event.clientX - dragStartPos.x;\n const deltaY = event.clientY - dragStartPos.y;\n\n const newLeft = nodeStartPos.left + deltaX;\n const newTop = nodeStartPos.top + deltaY;\n\n // Update position\n (testElement as HTMLElement).style.left = `${newLeft}px`;\n (testElement as HTMLElement).style.top = `${newTop}px`;\n };\n\n const handleMouseUp = (event: MouseEvent) => {\n if (!isDragging) return;\n\n isDragging = false;\n dragEnded = true;\n\n const deltaX = event.clientX - dragStartPos.x;\n const deltaY = event.clientY - dragStartPos.y;\n\n newPosition = {\n left: nodeStartPos.left + deltaX,\n top: nodeStartPos.top + deltaY\n };\n };\n\n // Add event listeners\n testElement.addEventListener('mousedown', handleMouseDown);\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n\n // Test drag from node (should work)\n const mouseDownEvent = new MouseEvent('mousedown', {\n clientX: 150,\n clientY: 150,\n bubbles: true\n });\n testElement.dispatchEvent(mouseDownEvent);\n\n assert.isTrue(dragStarted, 'Drag should start when clicking on node');\n\n const mouseMoveEvent = new MouseEvent('mousemove', {\n clientX: 200,\n clientY: 200,\n bubbles: true\n });\n document.dispatchEvent(mouseMoveEvent);\n\n const mouseUpEvent = new MouseEvent('mouseup', {\n clientX: 200,\n clientY: 200,\n bubbles: true\n });\n document.dispatchEvent(mouseUpEvent);\n\n assert.isTrue(dragEnded, 'Drag should end on mouse up');\n assert.equal(\n newPosition.left,\n 150,\n 'New left position should be calculated correctly'\n );\n assert.equal(\n newPosition.top,\n 150,\n 'New top position should be calculated correctly'\n );\n\n // Test that position was visually updated\n assert.equal((testElement as HTMLElement).style.left, '150px');\n assert.equal((testElement as HTMLElement).style.top, '150px');\n\n // Reset for next test\n dragStarted = false;\n dragEnded = false;\n\n // Test drag from exit element (should not work)\n const exitElement = testElement.querySelector('.exit') as HTMLElement;\n const exitMouseDownEvent = new MouseEvent('mousedown', {\n clientX: 150,\n clientY: 150,\n bubbles: true\n });\n exitElement.dispatchEvent(exitMouseDownEvent);\n\n assert.isFalse(\n dragStarted,\n 'Drag should not start when clicking on exit element'\n );\n\n // Clean up\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n });\n\n it('should snap node positions to 20px grid during drag and drop', async () => {\n // Create a test node element\n const testElement = await fixture(html`\n <div\n class=\"node\"\n style=\"position: absolute; left: 100px; top: 100px; width: 200px; height: 100px; background: white; border: 1px solid #ccc; cursor: move;\"\n >\n Test Node for Grid Snapping\n </div>\n `);\n\n // Helper function to snap values to 20px grid (same as implementation)\n const snapToGrid = (value: number): number => {\n return Math.round(value / 20) * 20;\n };\n\n // Simulate drag implementation with grid snapping\n let isDragging = false;\n let dragStartPos = { x: 0, y: 0 };\n let nodeStartPos = { left: 100, top: 100 };\n let finalPosition = { left: 0, top: 0 };\n\n const handleMouseDown = (event: MouseEvent) => {\n isDragging = true;\n dragStartPos = { x: event.clientX, y: event.clientY };\n // Get current position from element style\n const currentLeft =\n parseInt((testElement as HTMLElement).style.left) || 0;\n const currentTop = parseInt((testElement as HTMLElement).style.top) || 0;\n nodeStartPos = { left: currentLeft, top: currentTop };\n event.preventDefault();\n event.stopPropagation();\n };\n\n const handleMouseMove = (event: MouseEvent) => {\n if (!isDragging) return;\n\n const deltaX = event.clientX - dragStartPos.x;\n const deltaY = event.clientY - dragStartPos.y;\n\n const newLeft = nodeStartPos.left + deltaX;\n const newTop = nodeStartPos.top + deltaY;\n\n // Snap to 20px grid\n const snappedLeft = snapToGrid(newLeft);\n const snappedTop = snapToGrid(newTop);\n\n // Update position with snapped values\n (testElement as HTMLElement).style.left = `${snappedLeft}px`;\n (testElement as HTMLElement).style.top = `${snappedTop}px`;\n };\n\n const handleMouseUp = (event: MouseEvent) => {\n if (!isDragging) return;\n isDragging = false;\n\n const deltaX = event.clientX - dragStartPos.x;\n const deltaY = event.clientY - dragStartPos.y;\n\n const newLeft = nodeStartPos.left + deltaX;\n const newTop = nodeStartPos.top + deltaY;\n\n // Snap final position to grid\n finalPosition = {\n left: snapToGrid(newLeft),\n top: snapToGrid(newTop)\n };\n };\n\n // Add event listeners\n testElement.addEventListener('mousedown', handleMouseDown);\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n\n // Test Case 1: Drag to position that should snap to grid\n // Starting at (100, 100), drag by (33, 27) pixels\n // Should result in (133, 127) -> snapped to (140, 120)\n const mouseDownEvent1 = new MouseEvent('mousedown', {\n clientX: 150,\n clientY: 150,\n bubbles: true\n });\n testElement.dispatchEvent(mouseDownEvent1);\n\n const mouseMoveEvent1 = new MouseEvent('mousemove', {\n clientX: 183, // 150 + 33\n clientY: 177, // 150 + 27\n bubbles: true\n });\n document.dispatchEvent(mouseMoveEvent1);\n\n // Check that position is snapped during drag\n assert.equal((testElement as HTMLElement).style.left, '140px');\n assert.equal((testElement as HTMLElement).style.top, '120px');\n\n const mouseUpEvent1 = new MouseEvent('mouseup', {\n clientX: 183,\n clientY: 177,\n bubbles: true\n });\n document.dispatchEvent(mouseUpEvent1);\n\n // Check final snapped position\n assert.equal(\n finalPosition.left,\n 140,\n 'Final left position should be snapped to 140px'\n );\n assert.equal(\n finalPosition.top,\n 120,\n 'Final top position should be snapped to 120px'\n );\n\n // Test Case 2: Test different snap scenarios\n // Reset position\n nodeStartPos = { left: 60, top: 80 };\n (testElement as HTMLElement).style.left = '60px';\n (testElement as HTMLElement).style.top = '80px';\n\n const mouseDownEvent2 = new MouseEvent('mousedown', {\n clientX: 100,\n clientY: 100,\n bubbles: true\n });\n testElement.dispatchEvent(mouseDownEvent2);\n\n // Drag by (15, 25) -> should snap to nearest 20px grid\n // (60 + 15, 80 + 25) = (75, 105) -> snapped to (80, 100)\n const mouseMoveEvent2 = new MouseEvent('mousemove', {\n clientX: 115, // 100 + 15\n clientY: 125, // 100 + 25\n bubbles: true\n });\n document.dispatchEvent(mouseMoveEvent2);\n\n assert.equal((testElement as HTMLElement).style.left, '80px');\n assert.equal((testElement as HTMLElement).style.top, '100px');\n\n const mouseUpEvent2 = new MouseEvent('mouseup', {\n clientX: 115,\n clientY: 125,\n bubbles: true\n });\n document.dispatchEvent(mouseUpEvent2);\n\n assert.equal(\n finalPosition.left,\n 80,\n 'Final left position should be snapped to 80px'\n );\n assert.equal(\n finalPosition.top,\n 100,\n 'Final top position should be snapped to 100px'\n );\n\n // Test Case 3: Test exact grid positions remain unchanged\n nodeStartPos = { left: 120, top: 160 }; // Already on grid\n (testElement as HTMLElement).style.left = '120px';\n (testElement as HTMLElement).style.top = '160px';\n\n const mouseDownEvent3 = new MouseEvent('mousedown', {\n clientX: 200,\n clientY: 200,\n bubbles: true\n });\n testElement.dispatchEvent(mouseDownEvent3);\n\n // Small movement that should stay on same grid position\n const mouseMoveEvent3 = new MouseEvent('mousemove', {\n clientX: 202, // +2 pixels\n clientY: 203, // +3 pixels\n bubbles: true\n });\n document.dispatchEvent(mouseMoveEvent3);\n\n // (120 + 2, 160 + 3) = (122, 163) -> snapped to (120, 160)\n assert.equal((testElement as HTMLElement).style.left, '120px');\n assert.equal((testElement as HTMLElement).style.top, '160px');\n\n const mouseUpEvent3 = new MouseEvent('mouseup', {\n clientX: 202,\n clientY: 203,\n bubbles: true\n });\n document.dispatchEvent(mouseUpEvent3);\n\n assert.equal(finalPosition.left, 120, 'Position should remain on grid');\n assert.equal(finalPosition.top, 160, 'Position should remain on grid');\n\n // Clean up\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n });\n\n describe('auto-scroll during drag', () => {\n const AUTO_SCROLL_EDGE_ZONE = 100;\n const AUTO_SCROLL_MAX_SPEED = 15;\n\n function calculateScrollSpeed(\n mousePos: number,\n edgeStart: number,\n edgeEnd: number\n ): { dx: number; dy: number } {\n const dx = 0;\n const dy = 0;\n\n // Left/top edge\n const distFromStart = mousePos - edgeStart;\n if (distFromStart >= 0 && distFromStart < AUTO_SCROLL_EDGE_ZONE) {\n const ratio = 1 - distFromStart / AUTO_SCROLL_EDGE_ZONE;\n return { dx: -(ratio * AUTO_SCROLL_MAX_SPEED), dy: 0 };\n }\n\n // Right/bottom edge\n const distFromEnd = edgeEnd - mousePos;\n if (distFromEnd >= 0 && distFromEnd < AUTO_SCROLL_EDGE_ZONE) {\n const ratio = 1 - distFromEnd / AUTO_SCROLL_EDGE_ZONE;\n return { dx: ratio * AUTO_SCROLL_MAX_SPEED, dy: 0 };\n }\n\n return { dx, dy };\n }\n\n it('should calculate scroll speed based on distance to edge', () => {\n const edgeStart = 0;\n const edgeEnd = 800;\n\n // At the very left edge (distance = 0), speed should be max\n let result = calculateScrollSpeed(0, edgeStart, edgeEnd);\n assert.equal(result.dx, -AUTO_SCROLL_MAX_SPEED);\n\n // At the edge zone boundary, speed should be 0\n result = calculateScrollSpeed(AUTO_SCROLL_EDGE_ZONE, edgeStart, edgeEnd);\n assert.equal(result.dx, 0);\n\n // Halfway into the zone, speed should be half of max\n result = calculateScrollSpeed(\n AUTO_SCROLL_EDGE_ZONE / 2,\n edgeStart,\n edgeEnd\n );\n assert.closeTo(result.dx, -AUTO_SCROLL_MAX_SPEED / 2, 0.01);\n\n // At the very right edge (distance = 0), speed should be max positive\n result = calculateScrollSpeed(800, edgeStart, edgeEnd);\n assert.equal(result.dx, AUTO_SCROLL_MAX_SPEED);\n\n // Halfway into the right edge zone\n result = calculateScrollSpeed(\n edgeEnd - AUTO_SCROLL_EDGE_ZONE / 2,\n edgeStart,\n edgeEnd\n );\n assert.closeTo(result.dx, AUTO_SCROLL_MAX_SPEED / 2, 0.01);\n\n // In the middle of the viewport, no scrolling\n result = calculateScrollSpeed(400, edgeStart, edgeEnd);\n assert.equal(result.dx, 0);\n });\n\n it('should not scroll when mouse is outside the viewport', () => {\n const edgeStart = 0;\n const edgeEnd = 800;\n\n // Mouse is to the left of the viewport\n const result = calculateScrollSpeed(-10, edgeStart, edgeEnd);\n assert.equal(result.dx, 0);\n });\n\n it('should account for scroll delta in drag position calculation', () => {\n // Simulate the formula: deltaX = (clientX - startX) + autoScrollDeltaX\n const dragStartX = 400;\n const currentClientX = 450;\n const autoScrollDeltaX = 200;\n\n const deltaX = currentClientX - dragStartX + autoScrollDeltaX;\n\n // Without auto-scroll, delta would be 50. With 200px of scroll, it's 250.\n assert.equal(deltaX, 250);\n\n const originalLeft = 100;\n const newLeft = originalLeft + deltaX;\n assert.equal(newLeft, 350);\n });\n\n it('should accumulate scroll deltas correctly over multiple frames', () => {\n let autoScrollDeltaX = 0;\n let autoScrollDeltaY = 0;\n\n // Simulate several frames of scrolling\n const scrollIncrements = [\n { dx: 5, dy: 3 },\n { dx: 8, dy: 6 },\n { dx: 10, dy: 9 },\n { dx: 0, dy: 12 } // only vertical scrolling\n ];\n\n scrollIncrements.forEach(({ dx, dy }) => {\n autoScrollDeltaX += dx;\n autoScrollDeltaY += dy;\n });\n\n assert.equal(autoScrollDeltaX, 23);\n assert.equal(autoScrollDeltaY, 30);\n });\n\n it('should clamp scroll delta when at scroll boundaries', () => {\n // Simulate a scroll container at its left boundary\n let scrollLeft = 0;\n const requestedDx = -10;\n\n const beforeScrollLeft = scrollLeft;\n scrollLeft = Math.max(0, scrollLeft + requestedDx);\n const actualDx = scrollLeft - beforeScrollLeft;\n\n // At the boundary, actual delta should be 0\n assert.equal(actualDx, 0);\n\n // Simulate a scroll container not at boundary\n scrollLeft = 50;\n const beforeScrollLeft2 = scrollLeft;\n scrollLeft = Math.max(0, scrollLeft + requestedDx);\n const actualDx2 = scrollLeft - beforeScrollLeft2;\n\n // Should have scrolled the full amount\n assert.equal(actualDx2, -10);\n });\n\n it('should reset scroll deltas after drag ends', () => {\n let autoScrollDeltaX = 150;\n let autoScrollDeltaY = 200;\n\n // Simulate drag end reset\n autoScrollDeltaX = 0;\n autoScrollDeltaY = 0;\n\n assert.equal(autoScrollDeltaX, 0);\n assert.equal(autoScrollDeltaY, 0);\n });\n\n it('should handle simultaneous horizontal and vertical auto-scroll', () => {\n // Simulate mouse in the bottom-right corner of the viewport\n const edgeEnd = 800;\n const mousePos = 780; // 20px from the right/bottom edge\n\n const distFromEnd = edgeEnd - mousePos;\n assert.isTrue(distFromEnd < AUTO_SCROLL_EDGE_ZONE);\n\n const ratio = 1 - distFromEnd / AUTO_SCROLL_EDGE_ZONE;\n const scrollSpeed = ratio * AUTO_SCROLL_MAX_SPEED;\n\n // Both axes should get the same speed when equidistant from edges\n assert.isAbove(scrollSpeed, 0);\n\n // Apply to both axes\n let autoScrollDeltaX = 0;\n let autoScrollDeltaY = 0;\n autoScrollDeltaX += scrollSpeed;\n autoScrollDeltaY += scrollSpeed;\n\n assert.equal(autoScrollDeltaX, autoScrollDeltaY);\n assert.isAbove(autoScrollDeltaX, 0);\n });\n });\n});\n"]}
@@ -94,6 +94,37 @@ describe('Plumber', () => {
94
94
  expect(plumber.sources.size).to.equal(0);
95
95
  });
96
96
  });
97
+ describe('zoom property', () => {
98
+ it('defaults to 1.0', () => {
99
+ expect(plumber.zoom).to.equal(1.0);
100
+ });
101
+ it('is writable', () => {
102
+ plumber.zoom = 0.75;
103
+ expect(plumber.zoom).to.equal(0.75);
104
+ });
105
+ });
106
+ describe('toCanvas', () => {
107
+ it('returns identity at zoom 1.0', () => {
108
+ plumber.zoom = 1.0;
109
+ expect(plumber.toCanvas(100)).to.equal(100);
110
+ });
111
+ it('divides by zoom at 0.5', () => {
112
+ plumber.zoom = 0.5;
113
+ expect(plumber.toCanvas(100)).to.equal(200);
114
+ });
115
+ it('handles minimum zoom 0.1', () => {
116
+ plumber.zoom = 0.1;
117
+ expect(plumber.toCanvas(50)).to.equal(500);
118
+ });
119
+ it('handles negative viewport diffs', () => {
120
+ plumber.zoom = 0.5;
121
+ expect(plumber.toCanvas(-100)).to.equal(-200);
122
+ });
123
+ it('handles zero', () => {
124
+ plumber.zoom = 0.5;
125
+ expect(plumber.toCanvas(0)).to.equal(0);
126
+ });
127
+ });
97
128
  });
98
129
  describe('calculateFlowchartPath', () => {
99
130
  it('generates a straight vertical path when source and target are aligned', () => {
@@ -1 +1 @@
1
- {"version":3,"file":"temba-flow-plumber.test.js","sourceRoot":"","sources":["../../test/temba-flow-plumber.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { Plumber, calculateFlowchartPath } from '../src/flow/Plumber';\nimport { stub, useFakeTimers, SinonFakeTimers } from 'sinon';\n\ndescribe('Plumber', () => {\n let plumber: Plumber;\n let mockCanvas: HTMLElement;\n let clock: SinonFakeTimers;\n let mockElement: HTMLElement;\n\n beforeEach(() => {\n clock = useFakeTimers();\n\n mockCanvas = document.createElement('div');\n mockCanvas.id = 'canvas';\n document.body.appendChild(mockCanvas);\n\n mockElement = document.createElement('div');\n mockElement.id = 'test-exit';\n mockCanvas.appendChild(mockElement);\n\n const mockEditor = { fireCustomEvent: stub() };\n plumber = new Plumber(mockCanvas, mockEditor);\n });\n\n afterEach(() => {\n mockCanvas.remove();\n clock.restore();\n });\n\n describe('constructor', () => {\n it('creates a new plumber instance', () => {\n expect(plumber).to.be.instanceOf(Plumber);\n });\n });\n\n describe('makeSource', () => {\n it('registers a mousedown listener on the exit element', () => {\n const exitEl = document.createElement('div');\n exitEl.id = 'exit-1';\n mockCanvas.appendChild(exitEl);\n\n plumber.makeSource('exit-1');\n\n // Source should be tracked\n expect((plumber as any).sources.has('exit-1')).to.be.true;\n\n exitEl.remove();\n });\n\n it('cleans up previous listener when called again', () => {\n const exitEl = document.createElement('div');\n exitEl.id = 'exit-2';\n mockCanvas.appendChild(exitEl);\n\n plumber.makeSource('exit-2');\n plumber.makeSource('exit-2');\n\n expect((plumber as any).sources.has('exit-2')).to.be.true;\n\n exitEl.remove();\n });\n });\n\n describe('makeTarget', () => {\n it('is a no-op', () => {\n // Should not throw\n plumber.makeTarget('test-node');\n });\n });\n\n describe('connectIds', () => {\n it('adds connection to pending connections and processes them', () => {\n plumber.connectIds('test-node', 'test-from', 'test-to');\n expect((plumber as any).pendingConnections.length).to.equal(1);\n });\n });\n\n describe('processPendingConnections', () => {\n it('clears existing rAF when called multiple times', () => {\n const cancelSpy = stub(window, 'cancelAnimationFrame');\n const rafSpy = stub(window, 'requestAnimationFrame').returns(123 as any);\n\n plumber.processPendingConnections();\n plumber.processPendingConnections();\n\n expect(cancelSpy).to.have.been.calledOnce;\n expect(rafSpy).to.have.been.calledTwice;\n\n cancelSpy.restore();\n rafSpy.restore();\n });\n });\n\n describe('event system', () => {\n it('supports on/off/notify pattern', () => {\n let received = null;\n const handler = (info: any) => {\n received = info;\n };\n\n plumber.on('test-event', handler);\n (plumber as any).notifyListeners('test-event', { data: 'test' });\n expect(received).to.deep.equal({ data: 'test' });\n\n received = null;\n plumber.off('test-event', handler);\n (plumber as any).notifyListeners('test-event', { data: 'test2' });\n expect(received).to.be.null;\n });\n });\n\n describe('reset', () => {\n it('clears all state', () => {\n plumber.connectIds('test-node', 'test-from', 'test-to');\n plumber.reset();\n\n expect((plumber as any).pendingConnections.length).to.equal(0);\n expect((plumber as any).connections.size).to.equal(0);\n expect((plumber as any).sources.size).to.equal(0);\n });\n });\n});\n\ndescribe('calculateFlowchartPath', () => {\n it('generates a straight vertical path when source and target are aligned', () => {\n const path = calculateFlowchartPath(100, 0, 100, 100);\n expect(path).to.include('M 100 0');\n expect(path).to.include('L 100 100');\n // Should not contain Q (quadratic curve) for aligned points\n expect(path).to.not.include('Q');\n });\n\n it('generates a path with corners when source and target are offset', () => {\n const path = calculateFlowchartPath(50, 0, 150, 200);\n expect(path).to.include('M 50 0');\n expect(path).to.include('Q'); // Should have rounded corners\n expect(path).to.include('L 150 200');\n });\n\n it('handles custom stub and corner radius', () => {\n const path = calculateFlowchartPath(0, 0, 100, 100, 30, 15, 10);\n expect(path).to.include('M 0 0');\n expect(path).to.include('L 100 100');\n });\n\n it('handles cases where vertical space is tight by using reduced-radius corners', () => {\n // With stubs of 20+10=30, and only 35 total vertical space, there's only 5px for corners\n const path = calculateFlowchartPath(50, 0, 150, 35);\n expect(path).to.include('M 50 0');\n // Should still use rounded corners (L-shape with curves)\n expect(path).to.include('Q');\n });\n\n it('enforces midY is always below source exit for top face', () => {\n // Target above source — midY should not go above sourceY + stubStart\n const path = calculateFlowchartPath(50, 100, 150, 50);\n expect(path).to.include('M 50 100');\n // Should still exit downward with a curve at exitY (120)\n expect(path).to.include('Q');\n });\n\n it('generates a path entering from the left face', () => {\n const path = calculateFlowchartPath(50, 0, 150, 100, 20, 10, 5, 'left');\n expect(path).to.include('M 50 0');\n expect(path).to.include('L 150 100'); // ends at target\n });\n\n it('generates a path entering from the right face', () => {\n const path = calculateFlowchartPath(150, 0, 50, 100, 20, 10, 5, 'right');\n expect(path).to.include('M 150 0');\n expect(path).to.include('L 50 100'); // ends at target\n });\n\n it('applies jogYOffset to shift the horizontal jog level', () => {\n const pathNoOffset = calculateFlowchartPath(\n 50,\n 0,\n 150,\n 200,\n 20,\n 10,\n 5,\n 'top',\n 0\n );\n const pathPosOffset = calculateFlowchartPath(\n 50,\n 0,\n 150,\n 200,\n 20,\n 10,\n 5,\n 'top',\n 10\n );\n const pathNegOffset = calculateFlowchartPath(\n 50,\n 0,\n 150,\n 200,\n 20,\n 10,\n 5,\n 'top',\n -10\n );\n expect(pathNoOffset).to.not.equal(pathPosOffset);\n expect(pathNoOffset).to.not.equal(pathNegOffset);\n expect(pathPosOffset).to.not.equal(pathNegOffset);\n });\n\n it('produces same path with jogYOffset=0 as without offset', () => {\n const pathDefault = calculateFlowchartPath(50, 0, 150, 200);\n const pathZero = calculateFlowchartPath(\n 50,\n 0,\n 150,\n 200,\n 20,\n 10,\n 5,\n 'top',\n 0\n );\n expect(pathDefault).to.equal(pathZero);\n });\n\n it('clamps jogYOffset to valid bounds', () => {\n // Large positive offset should not push jogY past entryY\n const path = calculateFlowchartPath(50, 0, 150, 50, 20, 10, 5, 'top', 1000);\n expect(path).to.include('M 50 0');\n expect(path).to.include('L 150 50'); // should still reach target\n });\n});\n"]}
1
+ {"version":3,"file":"temba-flow-plumber.test.js","sourceRoot":"","sources":["../../test/temba-flow-plumber.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { Plumber, calculateFlowchartPath } from '../src/flow/Plumber';\nimport { stub, useFakeTimers, SinonFakeTimers } from 'sinon';\n\ndescribe('Plumber', () => {\n let plumber: Plumber;\n let mockCanvas: HTMLElement;\n let clock: SinonFakeTimers;\n let mockElement: HTMLElement;\n\n beforeEach(() => {\n clock = useFakeTimers();\n\n mockCanvas = document.createElement('div');\n mockCanvas.id = 'canvas';\n document.body.appendChild(mockCanvas);\n\n mockElement = document.createElement('div');\n mockElement.id = 'test-exit';\n mockCanvas.appendChild(mockElement);\n\n const mockEditor = { fireCustomEvent: stub() };\n plumber = new Plumber(mockCanvas, mockEditor);\n });\n\n afterEach(() => {\n mockCanvas.remove();\n clock.restore();\n });\n\n describe('constructor', () => {\n it('creates a new plumber instance', () => {\n expect(plumber).to.be.instanceOf(Plumber);\n });\n });\n\n describe('makeSource', () => {\n it('registers a mousedown listener on the exit element', () => {\n const exitEl = document.createElement('div');\n exitEl.id = 'exit-1';\n mockCanvas.appendChild(exitEl);\n\n plumber.makeSource('exit-1');\n\n // Source should be tracked\n expect((plumber as any).sources.has('exit-1')).to.be.true;\n\n exitEl.remove();\n });\n\n it('cleans up previous listener when called again', () => {\n const exitEl = document.createElement('div');\n exitEl.id = 'exit-2';\n mockCanvas.appendChild(exitEl);\n\n plumber.makeSource('exit-2');\n plumber.makeSource('exit-2');\n\n expect((plumber as any).sources.has('exit-2')).to.be.true;\n\n exitEl.remove();\n });\n });\n\n describe('makeTarget', () => {\n it('is a no-op', () => {\n // Should not throw\n plumber.makeTarget('test-node');\n });\n });\n\n describe('connectIds', () => {\n it('adds connection to pending connections and processes them', () => {\n plumber.connectIds('test-node', 'test-from', 'test-to');\n expect((plumber as any).pendingConnections.length).to.equal(1);\n });\n });\n\n describe('processPendingConnections', () => {\n it('clears existing rAF when called multiple times', () => {\n const cancelSpy = stub(window, 'cancelAnimationFrame');\n const rafSpy = stub(window, 'requestAnimationFrame').returns(123 as any);\n\n plumber.processPendingConnections();\n plumber.processPendingConnections();\n\n expect(cancelSpy).to.have.been.calledOnce;\n expect(rafSpy).to.have.been.calledTwice;\n\n cancelSpy.restore();\n rafSpy.restore();\n });\n });\n\n describe('event system', () => {\n it('supports on/off/notify pattern', () => {\n let received = null;\n const handler = (info: any) => {\n received = info;\n };\n\n plumber.on('test-event', handler);\n (plumber as any).notifyListeners('test-event', { data: 'test' });\n expect(received).to.deep.equal({ data: 'test' });\n\n received = null;\n plumber.off('test-event', handler);\n (plumber as any).notifyListeners('test-event', { data: 'test2' });\n expect(received).to.be.null;\n });\n });\n\n describe('reset', () => {\n it('clears all state', () => {\n plumber.connectIds('test-node', 'test-from', 'test-to');\n plumber.reset();\n\n expect((plumber as any).pendingConnections.length).to.equal(0);\n expect((plumber as any).connections.size).to.equal(0);\n expect((plumber as any).sources.size).to.equal(0);\n });\n });\n\n describe('zoom property', () => {\n it('defaults to 1.0', () => {\n expect(plumber.zoom).to.equal(1.0);\n });\n\n it('is writable', () => {\n plumber.zoom = 0.75;\n expect(plumber.zoom).to.equal(0.75);\n });\n });\n\n describe('toCanvas', () => {\n it('returns identity at zoom 1.0', () => {\n plumber.zoom = 1.0;\n expect((plumber as any).toCanvas(100)).to.equal(100);\n });\n\n it('divides by zoom at 0.5', () => {\n plumber.zoom = 0.5;\n expect((plumber as any).toCanvas(100)).to.equal(200);\n });\n\n it('handles minimum zoom 0.1', () => {\n plumber.zoom = 0.1;\n expect((plumber as any).toCanvas(50)).to.equal(500);\n });\n\n it('handles negative viewport diffs', () => {\n plumber.zoom = 0.5;\n expect((plumber as any).toCanvas(-100)).to.equal(-200);\n });\n\n it('handles zero', () => {\n plumber.zoom = 0.5;\n expect((plumber as any).toCanvas(0)).to.equal(0);\n });\n });\n});\n\ndescribe('calculateFlowchartPath', () => {\n it('generates a straight vertical path when source and target are aligned', () => {\n const path = calculateFlowchartPath(100, 0, 100, 100);\n expect(path).to.include('M 100 0');\n expect(path).to.include('L 100 100');\n // Should not contain Q (quadratic curve) for aligned points\n expect(path).to.not.include('Q');\n });\n\n it('generates a path with corners when source and target are offset', () => {\n const path = calculateFlowchartPath(50, 0, 150, 200);\n expect(path).to.include('M 50 0');\n expect(path).to.include('Q'); // Should have rounded corners\n expect(path).to.include('L 150 200');\n });\n\n it('handles custom stub and corner radius', () => {\n const path = calculateFlowchartPath(0, 0, 100, 100, 30, 15, 10);\n expect(path).to.include('M 0 0');\n expect(path).to.include('L 100 100');\n });\n\n it('handles cases where vertical space is tight by using reduced-radius corners', () => {\n // With stubs of 20+10=30, and only 35 total vertical space, there's only 5px for corners\n const path = calculateFlowchartPath(50, 0, 150, 35);\n expect(path).to.include('M 50 0');\n // Should still use rounded corners (L-shape with curves)\n expect(path).to.include('Q');\n });\n\n it('enforces midY is always below source exit for top face', () => {\n // Target above source — midY should not go above sourceY + stubStart\n const path = calculateFlowchartPath(50, 100, 150, 50);\n expect(path).to.include('M 50 100');\n // Should still exit downward with a curve at exitY (120)\n expect(path).to.include('Q');\n });\n\n it('generates a path entering from the left face', () => {\n const path = calculateFlowchartPath(50, 0, 150, 100, 20, 10, 5, 'left');\n expect(path).to.include('M 50 0');\n expect(path).to.include('L 150 100'); // ends at target\n });\n\n it('generates a path entering from the right face', () => {\n const path = calculateFlowchartPath(150, 0, 50, 100, 20, 10, 5, 'right');\n expect(path).to.include('M 150 0');\n expect(path).to.include('L 50 100'); // ends at target\n });\n\n it('applies jogYOffset to shift the horizontal jog level', () => {\n const pathNoOffset = calculateFlowchartPath(\n 50,\n 0,\n 150,\n 200,\n 20,\n 10,\n 5,\n 'top',\n 0\n );\n const pathPosOffset = calculateFlowchartPath(\n 50,\n 0,\n 150,\n 200,\n 20,\n 10,\n 5,\n 'top',\n 10\n );\n const pathNegOffset = calculateFlowchartPath(\n 50,\n 0,\n 150,\n 200,\n 20,\n 10,\n 5,\n 'top',\n -10\n );\n expect(pathNoOffset).to.not.equal(pathPosOffset);\n expect(pathNoOffset).to.not.equal(pathNegOffset);\n expect(pathPosOffset).to.not.equal(pathNegOffset);\n });\n\n it('produces same path with jogYOffset=0 as without offset', () => {\n const pathDefault = calculateFlowchartPath(50, 0, 150, 200);\n const pathZero = calculateFlowchartPath(\n 50,\n 0,\n 150,\n 200,\n 20,\n 10,\n 5,\n 'top',\n 0\n );\n expect(pathDefault).to.equal(pathZero);\n });\n\n it('clamps jogYOffset to valid bounds', () => {\n // Large positive offset should not push jogY past entryY\n const path = calculateFlowchartPath(50, 0, 150, 50, 20, 10, 5, 'top', 1000);\n expect(path).to.include('M 50 0');\n expect(path).to.include('L 150 50'); // should still reach target\n });\n});\n"]}