@nyaruka/temba-components 0.131.0 → 0.131.2

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 (430) hide show
  1. package/.github/workflows/publish.yml +4 -1
  2. package/CHANGELOG.md +67 -1
  3. package/demo/data/flows/food-order.json +2 -2
  4. package/demo/data/flows/sample-flow.json +74 -125
  5. package/dist/static/svg/index.svg +1 -1
  6. package/dist/temba-components.js +1156 -619
  7. package/dist/temba-components.js.map +1 -1
  8. package/out-tsc/src/Icons.js +4 -1
  9. package/out-tsc/src/Icons.js.map +1 -1
  10. package/out-tsc/src/events.js.map +1 -1
  11. package/out-tsc/src/flow/CanvasMenu.js +200 -0
  12. package/out-tsc/src/flow/CanvasMenu.js.map +1 -0
  13. package/out-tsc/src/flow/CanvasNode.js +327 -19
  14. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  15. package/out-tsc/src/flow/Editor.js +562 -66
  16. package/out-tsc/src/flow/Editor.js.map +1 -1
  17. package/out-tsc/src/flow/NodeEditor.js +240 -93
  18. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  19. package/out-tsc/src/flow/NodeTypeSelector.js +499 -0
  20. package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -0
  21. package/out-tsc/src/flow/actions/add_contact_groups.js +3 -3
  22. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
  23. package/out-tsc/src/flow/actions/add_contact_urn.js +62 -4
  24. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
  25. package/out-tsc/src/flow/actions/add_input_labels.js +3 -3
  26. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  27. package/out-tsc/src/flow/actions/play_audio.js +2 -2
  28. package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
  29. package/out-tsc/src/flow/actions/remove_contact_groups.js +6 -5
  30. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
  31. package/out-tsc/src/flow/actions/request_optin.js +2 -2
  32. package/out-tsc/src/flow/actions/request_optin.js.map +1 -1
  33. package/out-tsc/src/flow/actions/say_msg.js +2 -2
  34. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  35. package/out-tsc/src/flow/actions/send_broadcast.js +76 -23
  36. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
  37. package/out-tsc/src/flow/actions/send_email.js +4 -5
  38. package/out-tsc/src/flow/actions/send_email.js.map +1 -1
  39. package/out-tsc/src/flow/actions/send_msg.js +9 -19
  40. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  41. package/out-tsc/src/flow/actions/set_contact_channel.js +5 -9
  42. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  43. package/out-tsc/src/flow/actions/set_contact_field.js +19 -20
  44. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  45. package/out-tsc/src/flow/actions/set_contact_language.js +2 -2
  46. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  47. package/out-tsc/src/flow/actions/set_contact_name.js +2 -12
  48. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
  49. package/out-tsc/src/flow/actions/set_contact_status.js +2 -2
  50. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  51. package/out-tsc/src/flow/actions/set_run_result.js +3 -3
  52. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  53. package/out-tsc/src/flow/actions/start_session.js +180 -6
  54. package/out-tsc/src/flow/actions/start_session.js.map +1 -1
  55. package/out-tsc/src/flow/config.js +11 -15
  56. package/out-tsc/src/flow/config.js.map +1 -1
  57. package/out-tsc/src/flow/currencies.js +45 -0
  58. package/out-tsc/src/flow/currencies.js.map +1 -0
  59. package/out-tsc/src/flow/nodes/shared-rules.js +257 -0
  60. package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -0
  61. package/out-tsc/src/flow/nodes/shared.js +17 -0
  62. package/out-tsc/src/flow/nodes/shared.js.map +1 -0
  63. package/out-tsc/src/flow/nodes/split_by_airtime.js +205 -5
  64. package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -1
  65. package/out-tsc/src/flow/nodes/split_by_contact_field.js +147 -3
  66. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
  67. package/out-tsc/src/flow/nodes/split_by_expression.js +68 -2
  68. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
  69. package/out-tsc/src/flow/nodes/split_by_groups.js +12 -9
  70. package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
  71. package/out-tsc/src/flow/nodes/split_by_intent.js +7 -0
  72. package/out-tsc/src/flow/nodes/split_by_intent.js.map +1 -0
  73. package/out-tsc/src/flow/nodes/split_by_llm.js +3 -2
  74. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
  75. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +2 -2
  76. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  77. package/out-tsc/src/flow/nodes/split_by_random.js +3 -3
  78. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
  79. package/out-tsc/src/flow/nodes/split_by_resthook.js +108 -0
  80. package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -0
  81. package/out-tsc/src/flow/nodes/split_by_run_result.js +206 -3
  82. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
  83. package/out-tsc/src/flow/nodes/split_by_scheme.js +153 -2
  84. package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -1
  85. package/out-tsc/src/flow/nodes/split_by_subflow.js +6 -4
  86. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
  87. package/out-tsc/src/flow/nodes/split_by_ticket.js +3 -2
  88. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
  89. package/out-tsc/src/flow/nodes/split_by_webhook.js +3 -2
  90. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
  91. package/out-tsc/src/flow/nodes/wait_for_audio.js +2 -2
  92. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -1
  93. package/out-tsc/src/flow/nodes/wait_for_digits.js +2 -2
  94. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  95. package/out-tsc/src/flow/nodes/wait_for_image.js +2 -2
  96. package/out-tsc/src/flow/nodes/wait_for_image.js.map +1 -1
  97. package/out-tsc/src/flow/nodes/wait_for_location.js +2 -2
  98. package/out-tsc/src/flow/nodes/wait_for_location.js.map +1 -1
  99. package/out-tsc/src/flow/nodes/wait_for_menu.js +2 -2
  100. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
  101. package/out-tsc/src/flow/nodes/wait_for_response.js +32 -567
  102. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  103. package/out-tsc/src/flow/nodes/wait_for_video.js +2 -2
  104. package/out-tsc/src/flow/nodes/wait_for_video.js.map +1 -1
  105. package/out-tsc/src/flow/types.js +71 -12
  106. package/out-tsc/src/flow/types.js.map +1 -1
  107. package/out-tsc/src/flow/utils.js +101 -14
  108. package/out-tsc/src/flow/utils.js.map +1 -1
  109. package/out-tsc/src/form/ContactSearch.js +1 -1
  110. package/out-tsc/src/form/ContactSearch.js.map +1 -1
  111. package/out-tsc/src/form/FieldRenderer.js +2 -4
  112. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  113. package/out-tsc/src/interfaces.js +3 -0
  114. package/out-tsc/src/interfaces.js.map +1 -1
  115. package/out-tsc/src/list/SortableList.js +98 -33
  116. package/out-tsc/src/list/SortableList.js.map +1 -1
  117. package/out-tsc/src/live/ContactChat.js +15 -18
  118. package/out-tsc/src/live/ContactChat.js.map +1 -1
  119. package/out-tsc/src/store/AppState.js +53 -0
  120. package/out-tsc/src/store/AppState.js.map +1 -1
  121. package/out-tsc/src/utils.js +254 -13
  122. package/out-tsc/src/utils.js.map +1 -1
  123. package/out-tsc/temba-modules.js +4 -0
  124. package/out-tsc/temba-modules.js.map +1 -1
  125. package/out-tsc/test/ActionHelper.js +3 -3
  126. package/out-tsc/test/ActionHelper.js.map +1 -1
  127. package/out-tsc/test/NodeHelper.js +6 -3
  128. package/out-tsc/test/NodeHelper.js.map +1 -1
  129. package/out-tsc/test/actions/add_contact_urn.test.js +202 -0
  130. package/out-tsc/test/actions/add_contact_urn.test.js.map +1 -0
  131. package/out-tsc/test/actions/send_broadcast.test.js +148 -0
  132. package/out-tsc/test/actions/send_broadcast.test.js.map +1 -0
  133. package/out-tsc/test/actions/send_email.test.js +17 -23
  134. package/out-tsc/test/actions/send_email.test.js.map +1 -1
  135. package/out-tsc/test/actions/send_msg.test.js +33 -15
  136. package/out-tsc/test/actions/send_msg.test.js.map +1 -1
  137. package/out-tsc/test/actions/start_session.test.js +116 -0
  138. package/out-tsc/test/actions/start_session.test.js.map +1 -0
  139. package/out-tsc/test/nodes/split_by_airtime.test.js +604 -0
  140. package/out-tsc/test/nodes/split_by_airtime.test.js.map +1 -0
  141. package/out-tsc/test/nodes/split_by_contact_field.test.js +387 -0
  142. package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -0
  143. package/out-tsc/test/nodes/split_by_expression.test.js +614 -0
  144. package/out-tsc/test/nodes/split_by_expression.test.js.map +1 -0
  145. package/out-tsc/test/nodes/split_by_random.test.js +3 -3
  146. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
  147. package/out-tsc/test/nodes/split_by_resthook.test.js +337 -0
  148. package/out-tsc/test/nodes/split_by_resthook.test.js.map +1 -0
  149. package/out-tsc/test/nodes/split_by_run_result.test.js +920 -0
  150. package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -0
  151. package/out-tsc/test/nodes/split_by_scheme.test.js +399 -0
  152. package/out-tsc/test/nodes/split_by_scheme.test.js.map +1 -0
  153. package/out-tsc/test/nodes/split_by_subflow.test.js +333 -0
  154. package/out-tsc/test/nodes/split_by_subflow.test.js.map +1 -0
  155. package/out-tsc/test/nodes/wait_for_digits.test.js +2 -2
  156. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  157. package/out-tsc/test/nodes/wait_for_response.test.js +2 -1
  158. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
  159. package/out-tsc/test/temba-action-drag-between-nodes.test.js +252 -0
  160. package/out-tsc/test/temba-action-drag-between-nodes.test.js.map +1 -0
  161. package/out-tsc/test/temba-canvas-menu.test.js +122 -0
  162. package/out-tsc/test/temba-canvas-menu.test.js.map +1 -0
  163. package/out-tsc/test/temba-flow-editor-node.test.js +85 -2
  164. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  165. package/out-tsc/test/temba-flow-editor.test.js +7 -8
  166. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  167. package/out-tsc/test/temba-node-editor.test.js +3 -1
  168. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  169. package/out-tsc/test/temba-node-type-selector.test.js +115 -0
  170. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -0
  171. package/out-tsc/test/temba-omnibox.test.js +2 -1
  172. package/out-tsc/test/temba-omnibox.test.js.map +1 -1
  173. package/out-tsc/test/temba-sortable-list.test.js +51 -0
  174. package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
  175. package/out-tsc/test/temba-utils-index.test.js +1 -27
  176. package/out-tsc/test/temba-utils-index.test.js.map +1 -1
  177. package/out-tsc/test/utils.test.js +2 -0
  178. package/out-tsc/test/utils.test.js.map +1 -1
  179. package/package.json +2 -1
  180. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  181. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  182. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  183. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  184. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  185. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  186. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  187. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  188. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  189. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  190. package/screenshots/truth/actions/add_contact_urn/editor/expression-facebook.png +0 -0
  191. package/screenshots/truth/actions/add_contact_urn/editor/expression-phone.png +0 -0
  192. package/screenshots/truth/actions/add_contact_urn/editor/facebook-id.png +0 -0
  193. package/screenshots/truth/actions/add_contact_urn/editor/instagram-handle.png +0 -0
  194. package/screenshots/truth/actions/add_contact_urn/editor/line-id.png +0 -0
  195. package/screenshots/truth/actions/add_contact_urn/editor/phone-number.png +0 -0
  196. package/screenshots/truth/actions/add_contact_urn/editor/telegram-id.png +0 -0
  197. package/screenshots/truth/actions/add_contact_urn/editor/viber-id.png +0 -0
  198. package/screenshots/truth/actions/add_contact_urn/editor/wechat-id.png +0 -0
  199. package/screenshots/truth/actions/add_contact_urn/editor/whatsapp.png +0 -0
  200. package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
  201. package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
  202. package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
  203. package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
  204. package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
  205. package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
  206. package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
  207. package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
  208. package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
  209. package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
  210. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  211. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  212. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  213. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  214. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  215. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  216. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  217. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  218. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  219. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  220. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  221. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  222. package/screenshots/truth/actions/send_broadcast/editor/contacts-only.png +0 -0
  223. package/screenshots/truth/actions/send_broadcast/editor/groups-and-contacts.png +0 -0
  224. package/screenshots/truth/actions/send_broadcast/editor/groups-only.png +0 -0
  225. package/screenshots/truth/actions/send_broadcast/editor/many-groups.png +0 -0
  226. package/screenshots/truth/actions/send_broadcast/editor/multiline-text.png +0 -0
  227. package/screenshots/truth/actions/send_broadcast/editor/with-attachments.png +0 -0
  228. package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
  229. package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
  230. package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
  231. package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
  232. package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
  233. package/screenshots/truth/actions/send_broadcast/render/with-attachments.png +0 -0
  234. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  235. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  236. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  237. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  238. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  239. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  240. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  241. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  242. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  243. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  244. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  245. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  246. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  247. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  248. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  249. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  250. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  251. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  252. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  253. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  254. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  255. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  256. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  257. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  258. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  259. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  260. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  261. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  262. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  263. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  264. package/screenshots/truth/actions/start_session/editor/contact-query.png +0 -0
  265. package/screenshots/truth/actions/start_session/editor/contacts-only.png +0 -0
  266. package/screenshots/truth/actions/start_session/editor/create-contact.png +0 -0
  267. package/screenshots/truth/actions/start_session/editor/groups-and-contacts.png +0 -0
  268. package/screenshots/truth/actions/start_session/editor/groups-only.png +0 -0
  269. package/screenshots/truth/actions/start_session/editor/many-recipients.png +0 -0
  270. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  271. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  272. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  273. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  274. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  275. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  276. package/screenshots/truth/canvas-menu/open.png +0 -0
  277. package/screenshots/truth/editor/router.png +0 -0
  278. package/screenshots/truth/editor/wait.png +0 -0
  279. package/screenshots/truth/list/fields-dragging.png +0 -0
  280. package/screenshots/truth/list/sortable-dragging.png +0 -0
  281. package/screenshots/truth/node-type-selector/action-mode.png +0 -0
  282. package/screenshots/truth/node-type-selector/split-mode.png +0 -0
  283. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  284. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  285. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  286. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  287. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  288. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  289. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  290. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  291. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  292. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  293. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  294. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  295. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  296. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  297. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  298. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  299. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  300. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  301. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  302. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  303. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  304. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  305. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  306. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  307. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  308. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  309. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  310. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  311. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  312. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  313. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  314. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  315. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  316. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  317. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  318. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  319. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  320. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  321. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  322. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  323. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  324. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  325. package/src/Icons.ts +4 -1
  326. package/src/events.ts +2 -6
  327. package/src/flow/CanvasMenu.ts +217 -0
  328. package/src/flow/CanvasNode.ts +408 -10
  329. package/src/flow/Editor.ts +683 -44
  330. package/src/flow/NodeEditor.ts +304 -125
  331. package/src/flow/NodeTypeSelector.ts +592 -0
  332. package/src/flow/actions/add_contact_groups.ts +4 -4
  333. package/src/flow/actions/add_contact_urn.ts +76 -4
  334. package/src/flow/actions/add_input_labels.ts +4 -4
  335. package/src/flow/actions/play_audio.ts +2 -2
  336. package/src/flow/actions/remove_contact_groups.ts +14 -6
  337. package/src/flow/actions/request_optin.ts +2 -2
  338. package/src/flow/actions/say_msg.ts +2 -2
  339. package/src/flow/actions/send_broadcast.ts +85 -23
  340. package/src/flow/actions/send_email.ts +10 -6
  341. package/src/flow/actions/send_msg.ts +22 -32
  342. package/src/flow/actions/set_contact_channel.ts +5 -11
  343. package/src/flow/actions/set_contact_field.ts +20 -25
  344. package/src/flow/actions/set_contact_language.ts +9 -4
  345. package/src/flow/actions/set_contact_name.ts +3 -15
  346. package/src/flow/actions/set_contact_status.ts +3 -3
  347. package/src/flow/actions/set_run_result.ts +4 -4
  348. package/src/flow/actions/start_session.ts +208 -6
  349. package/src/flow/config.ts +13 -15
  350. package/src/flow/currencies.ts +51 -0
  351. package/src/flow/nodes/shared-rules.ts +301 -0
  352. package/src/flow/nodes/shared.ts +18 -0
  353. package/src/flow/nodes/split_by_airtime.ts +238 -5
  354. package/src/flow/nodes/split_by_contact_field.ts +185 -3
  355. package/src/flow/nodes/split_by_expression.ts +94 -2
  356. package/src/flow/nodes/split_by_groups.ts +15 -10
  357. package/src/flow/nodes/split_by_intent.ts +7 -0
  358. package/src/flow/nodes/split_by_llm.ts +4 -3
  359. package/src/flow/nodes/split_by_llm_categorize.ts +4 -4
  360. package/src/flow/nodes/split_by_random.ts +5 -5
  361. package/src/flow/nodes/split_by_resthook.ts +130 -0
  362. package/src/flow/nodes/split_by_run_result.ts +249 -3
  363. package/src/flow/nodes/split_by_scheme.ts +192 -2
  364. package/src/flow/nodes/split_by_subflow.ts +6 -4
  365. package/src/flow/nodes/split_by_ticket.ts +4 -3
  366. package/src/flow/nodes/split_by_webhook.ts +6 -5
  367. package/src/flow/nodes/wait_for_audio.ts +2 -2
  368. package/src/flow/nodes/wait_for_digits.ts +2 -2
  369. package/src/flow/nodes/wait_for_image.ts +2 -2
  370. package/src/flow/nodes/wait_for_location.ts +2 -2
  371. package/src/flow/nodes/wait_for_menu.ts +2 -2
  372. package/src/flow/nodes/wait_for_response.ts +48 -679
  373. package/src/flow/nodes/wait_for_video.ts +2 -2
  374. package/src/flow/types.ts +109 -23
  375. package/src/flow/utils.ts +108 -14
  376. package/src/form/ContactSearch.ts +1 -1
  377. package/src/form/FieldRenderer.ts +2 -4
  378. package/src/interfaces.ts +3 -0
  379. package/src/list/SortableList.ts +109 -34
  380. package/src/live/ContactChat.ts +15 -18
  381. package/src/store/AppState.ts +69 -0
  382. package/src/store/flow-definition.d.ts +2 -5
  383. package/src/utils.ts +332 -12
  384. package/static/api/channels.json +46 -0
  385. package/static/api/resthooks.json +31 -0
  386. package/static/svg/index.svg +1 -1
  387. package/static/svg/work/traced/lightning-02.svg +1 -0
  388. package/static/svg/work/used/lightning-02.svg +3 -0
  389. package/temba-modules.ts +4 -0
  390. package/test/ActionHelper.ts +3 -3
  391. package/test/NodeHelper.ts +6 -3
  392. package/test/actions/add_contact_urn.test.ts +287 -0
  393. package/test/actions/send_broadcast.test.ts +190 -0
  394. package/test/actions/send_email.test.ts +17 -23
  395. package/test/actions/send_msg.test.ts +39 -15
  396. package/test/actions/start_session.test.ts +151 -0
  397. package/test/nodes/split_by_airtime.test.ts +673 -0
  398. package/test/nodes/split_by_contact_field.test.ts +451 -0
  399. package/test/nodes/split_by_expression.test.ts +751 -0
  400. package/test/nodes/split_by_random.test.ts +3 -3
  401. package/test/nodes/split_by_resthook.test.ts +398 -0
  402. package/test/nodes/split_by_run_result.test.ts +1109 -0
  403. package/test/nodes/split_by_scheme.test.ts +486 -0
  404. package/test/nodes/split_by_subflow.test.ts +381 -0
  405. package/test/nodes/wait_for_digits.test.ts +2 -2
  406. package/test/nodes/wait_for_response.test.ts +2 -1
  407. package/test/temba-action-drag-between-nodes.test.ts +301 -0
  408. package/test/temba-canvas-menu.test.ts +156 -0
  409. package/test/temba-flow-editor-node.test.ts +102 -2
  410. package/test/temba-flow-editor.test.ts +7 -8
  411. package/test/temba-node-editor.test.ts +3 -1
  412. package/test/temba-node-type-selector.test.ts +152 -0
  413. package/test/temba-omnibox.test.ts +2 -1
  414. package/test/temba-sortable-list.test.ts +69 -0
  415. package/test/temba-utils-index.test.ts +0 -35
  416. package/test/utils.test.ts +2 -0
  417. package/test-assets/contacts/history.json +14 -20
  418. package/web-dev-server.config.mjs +3 -1
  419. package/out-tsc/src/flow/actions/call_classifier.js +0 -11
  420. package/out-tsc/src/flow/actions/call_classifier.js.map +0 -1
  421. package/out-tsc/src/flow/actions/call_resthook.js +0 -11
  422. package/out-tsc/src/flow/actions/call_resthook.js.map +0 -1
  423. package/out-tsc/src/flow/actions/split_by_expression_example.js +0 -77
  424. package/out-tsc/src/flow/actions/split_by_expression_example.js.map +0 -1
  425. package/out-tsc/src/flow/actions/transfer_airtime.js +0 -11
  426. package/out-tsc/src/flow/actions/transfer_airtime.js.map +0 -1
  427. package/src/flow/actions/call_classifier.ts +0 -12
  428. package/src/flow/actions/call_resthook.ts +0 -12
  429. package/src/flow/actions/split_by_expression_example.ts +0 -88
  430. package/src/flow/actions/transfer_airtime.ts +0 -12
@@ -0,0 +1,252 @@
1
+ import { expect, fixture, html } from '@open-wc/testing';
2
+ import '../temba-modules';
3
+ describe('Drag actions between nodes', () => {
4
+ let node1;
5
+ let node1UI;
6
+ let node2;
7
+ let node2UI;
8
+ beforeEach(() => {
9
+ // Create test nodes
10
+ node1 = {
11
+ uuid: 'node-1',
12
+ actions: [
13
+ {
14
+ type: 'send_msg',
15
+ uuid: 'action-1',
16
+ text: 'First message',
17
+ quick_replies: []
18
+ },
19
+ {
20
+ type: 'send_msg',
21
+ uuid: 'action-2',
22
+ text: 'Second message',
23
+ quick_replies: []
24
+ }
25
+ ],
26
+ exits: [{ uuid: 'exit-1', destination_uuid: null }]
27
+ };
28
+ node1UI = {
29
+ position: { left: 100, top: 100 },
30
+ type: 'execute_actions',
31
+ config: {}
32
+ };
33
+ node2 = {
34
+ uuid: 'node-2',
35
+ actions: [
36
+ {
37
+ type: 'send_msg',
38
+ uuid: 'action-3',
39
+ text: 'Third message',
40
+ quick_replies: []
41
+ }
42
+ ],
43
+ exits: [{ uuid: 'exit-2', destination_uuid: null }]
44
+ };
45
+ node2UI = {
46
+ position: { left: 400, top: 100 },
47
+ type: 'execute_actions',
48
+ config: {}
49
+ };
50
+ });
51
+ it('should render execute_actions node with sortable list', async () => {
52
+ const node1Element = await fixture(html `
53
+ <temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>
54
+ `);
55
+ await node1Element.updateComplete;
56
+ // Verify node renders with sortable list
57
+ const sortableList = node1Element.querySelector('temba-sortable-list');
58
+ expect(sortableList).to.exist;
59
+ // Verify actions are rendered
60
+ const actions = node1Element.querySelectorAll('.action.sortable');
61
+ expect(actions.length).to.equal(2);
62
+ });
63
+ it('should show placeholder in target node during drag', async () => {
64
+ const node2Element = await fixture(html `
65
+ <temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>
66
+ `);
67
+ await node2Element.updateComplete;
68
+ // Simulate action-drag-over event from Editor
69
+ const dragOverEvent = new CustomEvent('action-drag-over', {
70
+ detail: {
71
+ action: node1.actions[0],
72
+ sourceNodeUuid: 'node-1',
73
+ actionIndex: 0,
74
+ mouseY: 150
75
+ },
76
+ bubbles: false
77
+ });
78
+ node2Element.dispatchEvent(dragOverEvent);
79
+ await node2Element.updateComplete;
80
+ // Check that placeholder is rendered
81
+ const placeholder = node2Element.querySelector('.drop-placeholder');
82
+ expect(placeholder).to.exist;
83
+ });
84
+ it('should handle drag-over event and store external drag info', async () => {
85
+ const node2Element = await fixture(html `
86
+ <temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>
87
+ `);
88
+ await node2Element.updateComplete;
89
+ // Simulate drag over
90
+ const dragOverEvent = new CustomEvent('action-drag-over', {
91
+ detail: {
92
+ action: node1.actions[0],
93
+ sourceNodeUuid: 'node-1',
94
+ actionIndex: 0,
95
+ mouseY: 150
96
+ },
97
+ bubbles: false
98
+ });
99
+ node2Element.dispatchEvent(dragOverEvent);
100
+ await node2Element.updateComplete;
101
+ // Verify that external drag info is stored (check internal state)
102
+ const externalDragInfo = node2Element.externalDragInfo;
103
+ expect(externalDragInfo).to.exist;
104
+ expect(externalDragInfo.action.uuid).to.equal('action-1');
105
+ expect(externalDragInfo.sourceNodeUuid).to.equal('node-1');
106
+ });
107
+ it('should calculate correct drop index based on mouse position', async () => {
108
+ const node2Element = await fixture(html `
109
+ <temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>
110
+ `);
111
+ await node2Element.updateComplete;
112
+ // Get action element bounds to calculate positions
113
+ const actionElement = node2Element.querySelector('.action.sortable');
114
+ expect(actionElement).to.exist;
115
+ const rect = actionElement.getBoundingClientRect();
116
+ const topY = rect.top + 5; // Near top of first action
117
+ const bottomY = rect.bottom + 5; // Below first action
118
+ // Drag over at top
119
+ const dragOverEventTop = new CustomEvent('action-drag-over', {
120
+ detail: {
121
+ action: node1.actions[0],
122
+ sourceNodeUuid: 'node-1',
123
+ actionIndex: 0,
124
+ mouseY: topY
125
+ },
126
+ bubbles: false
127
+ });
128
+ node2Element.dispatchEvent(dragOverEventTop);
129
+ await node2Element.updateComplete;
130
+ // Check drop index is at beginning
131
+ let externalDragInfo = node2Element.externalDragInfo;
132
+ expect(externalDragInfo.dropIndex).to.equal(0);
133
+ // Drag over at bottom
134
+ const dragOverEventBottom = new CustomEvent('action-drag-over', {
135
+ detail: {
136
+ action: node1.actions[0],
137
+ sourceNodeUuid: 'node-1',
138
+ actionIndex: 0,
139
+ mouseY: bottomY
140
+ },
141
+ bubbles: false
142
+ });
143
+ node2Element.dispatchEvent(dragOverEventBottom);
144
+ await node2Element.updateComplete;
145
+ // Check drop index is at end
146
+ externalDragInfo = node2Element.externalDragInfo;
147
+ expect(externalDragInfo.dropIndex).to.equal(1);
148
+ });
149
+ it('should not accept drops from the same node', async () => {
150
+ const node1Element = await fixture(html `
151
+ <temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>
152
+ `);
153
+ await node1Element.updateComplete;
154
+ // Try to drop action from node-1 onto node-1
155
+ const dragOverEvent = new CustomEvent('action-drag-over', {
156
+ detail: {
157
+ action: node1.actions[0],
158
+ sourceNodeUuid: 'node-1', // Same as target
159
+ actionIndex: 0,
160
+ mouseY: 150
161
+ },
162
+ bubbles: false
163
+ });
164
+ node1Element.dispatchEvent(dragOverEvent);
165
+ await node1Element.updateComplete;
166
+ // Verify external drag info was NOT stored (rejected due to same source)
167
+ const externalDragInfo = node1Element.externalDragInfo;
168
+ expect(externalDragInfo).to.be.null;
169
+ });
170
+ it('should clear external drag state and hide placeholder', async () => {
171
+ const node2Element = await fixture(html `
172
+ <temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>
173
+ `);
174
+ await node2Element.updateComplete;
175
+ // Simulate drag over
176
+ const dragOverEvent = new CustomEvent('action-drag-over', {
177
+ detail: {
178
+ action: node1.actions[0],
179
+ sourceNodeUuid: 'node-1',
180
+ actionIndex: 0,
181
+ mouseY: 150
182
+ },
183
+ bubbles: false
184
+ });
185
+ node2Element.dispatchEvent(dragOverEvent);
186
+ await node2Element.updateComplete;
187
+ // Verify placeholder exists
188
+ let placeholder = node2Element.querySelector('.drop-placeholder');
189
+ expect(placeholder).to.exist;
190
+ // Clear the external drag state (simulating drag leaving the node)
191
+ node2Element.externalDragInfo = null;
192
+ // Trigger re-render
193
+ node2Element.requestUpdate();
194
+ await node2Element.updateComplete;
195
+ // Verify placeholder is gone
196
+ placeholder = node2Element.querySelector('.drop-placeholder');
197
+ expect(placeholder).to.not.exist;
198
+ });
199
+ it('should have sortable list for internal drag support', async () => {
200
+ const node1Element = await fixture(html `
201
+ <temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>
202
+ `);
203
+ await node1Element.updateComplete;
204
+ // Verify sortable list exists for internal drag (reordering within same node)
205
+ const sortableList = node1Element.querySelector('temba-sortable-list');
206
+ expect(sortableList).to.exist;
207
+ // Verify it has external drag enabled
208
+ expect(sortableList.hasAttribute('externaldrag')).to.be.true;
209
+ });
210
+ it('should only accept drops on execute_actions nodes', async () => {
211
+ const routerNodeUI = {
212
+ position: { left: 100, top: 100 },
213
+ type: 'split_by_expression', // Not execute_actions
214
+ config: {}
215
+ };
216
+ const routerNode = {
217
+ uuid: 'router-node',
218
+ actions: [],
219
+ exits: [{ uuid: 'exit-1', destination_uuid: null }],
220
+ router: {
221
+ type: 'switch',
222
+ operand: '@input',
223
+ cases: [],
224
+ categories: [],
225
+ default_category_uuid: 'cat-1'
226
+ }
227
+ };
228
+ const routerElement = await fixture(html `
229
+ <temba-flow-node
230
+ .node=${routerNode}
231
+ .ui=${routerNodeUI}
232
+ ></temba-flow-node>
233
+ `);
234
+ await routerElement.updateComplete;
235
+ // Try to drag over a non-execute_actions node
236
+ const dragOverEvent = new CustomEvent('action-drag-over', {
237
+ detail: {
238
+ action: node1.actions[0],
239
+ sourceNodeUuid: 'node-1',
240
+ actionIndex: 0,
241
+ mouseY: 150
242
+ },
243
+ bubbles: false
244
+ });
245
+ routerElement.dispatchEvent(dragOverEvent);
246
+ await routerElement.updateComplete;
247
+ // Verify external drag info was NOT stored (rejected due to wrong node type)
248
+ const externalDragInfo = routerElement.externalDragInfo;
249
+ expect(externalDragInfo).to.be.null;
250
+ });
251
+ });
252
+ //# sourceMappingURL=temba-action-drag-between-nodes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temba-action-drag-between-nodes.test.js","sourceRoot":"","sources":["../../test/temba-action-drag-between-nodes.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture, html } from '@open-wc/testing';\nimport { CanvasNode } from '../src/flow/CanvasNode';\nimport { Node, NodeUI, SendMsg } from '../src/store/flow-definition';\nimport '../temba-modules';\n\ndescribe('Drag actions between nodes', () => {\n let node1: Node;\n let node1UI: NodeUI;\n let node2: Node;\n let node2UI: NodeUI;\n\n beforeEach(() => {\n // Create test nodes\n node1 = {\n uuid: 'node-1',\n actions: [\n {\n type: 'send_msg',\n uuid: 'action-1',\n text: 'First message',\n quick_replies: []\n } as SendMsg,\n {\n type: 'send_msg',\n uuid: 'action-2',\n text: 'Second message',\n quick_replies: []\n } as SendMsg\n ],\n exits: [{ uuid: 'exit-1', destination_uuid: null }]\n };\n\n node1UI = {\n position: { left: 100, top: 100 },\n type: 'execute_actions',\n config: {}\n };\n\n node2 = {\n uuid: 'node-2',\n actions: [\n {\n type: 'send_msg',\n uuid: 'action-3',\n text: 'Third message',\n quick_replies: []\n } as SendMsg\n ],\n exits: [{ uuid: 'exit-2', destination_uuid: null }]\n };\n\n node2UI = {\n position: { left: 400, top: 100 },\n type: 'execute_actions',\n config: {}\n };\n });\n\n it('should render execute_actions node with sortable list', async () => {\n const node1Element = await fixture<CanvasNode>(html`\n <temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>\n `);\n\n await node1Element.updateComplete;\n\n // Verify node renders with sortable list\n const sortableList = node1Element.querySelector('temba-sortable-list');\n expect(sortableList).to.exist;\n\n // Verify actions are rendered\n const actions = node1Element.querySelectorAll('.action.sortable');\n expect(actions.length).to.equal(2);\n });\n\n it('should show placeholder in target node during drag', async () => {\n const node2Element = await fixture<CanvasNode>(html`\n <temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>\n `);\n\n await node2Element.updateComplete;\n\n // Simulate action-drag-over event from Editor\n const dragOverEvent = new CustomEvent('action-drag-over', {\n detail: {\n action: node1.actions[0],\n sourceNodeUuid: 'node-1',\n actionIndex: 0,\n mouseY: 150\n },\n bubbles: false\n });\n\n node2Element.dispatchEvent(dragOverEvent);\n await node2Element.updateComplete;\n\n // Check that placeholder is rendered\n const placeholder = node2Element.querySelector('.drop-placeholder');\n expect(placeholder).to.exist;\n });\n\n it('should handle drag-over event and store external drag info', async () => {\n const node2Element = await fixture<CanvasNode>(html`\n <temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>\n `);\n\n await node2Element.updateComplete;\n\n // Simulate drag over\n const dragOverEvent = new CustomEvent('action-drag-over', {\n detail: {\n action: node1.actions[0],\n sourceNodeUuid: 'node-1',\n actionIndex: 0,\n mouseY: 150\n },\n bubbles: false\n });\n node2Element.dispatchEvent(dragOverEvent);\n await node2Element.updateComplete;\n\n // Verify that external drag info is stored (check internal state)\n const externalDragInfo = (node2Element as any).externalDragInfo;\n expect(externalDragInfo).to.exist;\n expect(externalDragInfo.action.uuid).to.equal('action-1');\n expect(externalDragInfo.sourceNodeUuid).to.equal('node-1');\n });\n\n it('should calculate correct drop index based on mouse position', async () => {\n const node2Element = await fixture<CanvasNode>(html`\n <temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>\n `);\n\n await node2Element.updateComplete;\n\n // Get action element bounds to calculate positions\n const actionElement = node2Element.querySelector(\n '.action.sortable'\n ) as HTMLElement;\n expect(actionElement).to.exist;\n\n const rect = actionElement.getBoundingClientRect();\n const topY = rect.top + 5; // Near top of first action\n const bottomY = rect.bottom + 5; // Below first action\n\n // Drag over at top\n const dragOverEventTop = new CustomEvent('action-drag-over', {\n detail: {\n action: node1.actions[0],\n sourceNodeUuid: 'node-1',\n actionIndex: 0,\n mouseY: topY\n },\n bubbles: false\n });\n node2Element.dispatchEvent(dragOverEventTop);\n await node2Element.updateComplete;\n\n // Check drop index is at beginning\n let externalDragInfo = (node2Element as any).externalDragInfo;\n expect(externalDragInfo.dropIndex).to.equal(0);\n\n // Drag over at bottom\n const dragOverEventBottom = new CustomEvent('action-drag-over', {\n detail: {\n action: node1.actions[0],\n sourceNodeUuid: 'node-1',\n actionIndex: 0,\n mouseY: bottomY\n },\n bubbles: false\n });\n node2Element.dispatchEvent(dragOverEventBottom);\n await node2Element.updateComplete;\n\n // Check drop index is at end\n externalDragInfo = (node2Element as any).externalDragInfo;\n expect(externalDragInfo.dropIndex).to.equal(1);\n });\n\n it('should not accept drops from the same node', async () => {\n const node1Element = await fixture<CanvasNode>(html`\n <temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>\n `);\n\n await node1Element.updateComplete;\n\n // Try to drop action from node-1 onto node-1\n const dragOverEvent = new CustomEvent('action-drag-over', {\n detail: {\n action: node1.actions[0],\n sourceNodeUuid: 'node-1', // Same as target\n actionIndex: 0,\n mouseY: 150\n },\n bubbles: false\n });\n node1Element.dispatchEvent(dragOverEvent);\n await node1Element.updateComplete;\n\n // Verify external drag info was NOT stored (rejected due to same source)\n const externalDragInfo = (node1Element as any).externalDragInfo;\n expect(externalDragInfo).to.be.null;\n });\n\n it('should clear external drag state and hide placeholder', async () => {\n const node2Element = await fixture<CanvasNode>(html`\n <temba-flow-node .node=${node2} .ui=${node2UI}></temba-flow-node>\n `);\n\n await node2Element.updateComplete;\n\n // Simulate drag over\n const dragOverEvent = new CustomEvent('action-drag-over', {\n detail: {\n action: node1.actions[0],\n sourceNodeUuid: 'node-1',\n actionIndex: 0,\n mouseY: 150\n },\n bubbles: false\n });\n node2Element.dispatchEvent(dragOverEvent);\n await node2Element.updateComplete;\n\n // Verify placeholder exists\n let placeholder = node2Element.querySelector('.drop-placeholder');\n expect(placeholder).to.exist;\n\n // Clear the external drag state (simulating drag leaving the node)\n (node2Element as any).externalDragInfo = null;\n // Trigger re-render\n node2Element.requestUpdate();\n await node2Element.updateComplete;\n\n // Verify placeholder is gone\n placeholder = node2Element.querySelector('.drop-placeholder');\n expect(placeholder).to.not.exist;\n });\n\n it('should have sortable list for internal drag support', async () => {\n const node1Element = await fixture<CanvasNode>(html`\n <temba-flow-node .node=${node1} .ui=${node1UI}></temba-flow-node>\n `);\n\n await node1Element.updateComplete;\n\n // Verify sortable list exists for internal drag (reordering within same node)\n const sortableList = node1Element.querySelector('temba-sortable-list');\n expect(sortableList).to.exist;\n\n // Verify it has external drag enabled\n expect(sortableList.hasAttribute('externaldrag')).to.be.true;\n });\n\n it('should only accept drops on execute_actions nodes', async () => {\n const routerNodeUI: NodeUI = {\n position: { left: 100, top: 100 },\n type: 'split_by_expression', // Not execute_actions\n config: {}\n };\n\n const routerNode: Node = {\n uuid: 'router-node',\n actions: [],\n exits: [{ uuid: 'exit-1', destination_uuid: null }],\n router: {\n type: 'switch',\n operand: '@input',\n cases: [],\n categories: [],\n default_category_uuid: 'cat-1'\n }\n };\n\n const routerElement = await fixture<CanvasNode>(html`\n <temba-flow-node\n .node=${routerNode}\n .ui=${routerNodeUI}\n ></temba-flow-node>\n `);\n\n await routerElement.updateComplete;\n\n // Try to drag over a non-execute_actions node\n const dragOverEvent = new CustomEvent('action-drag-over', {\n detail: {\n action: node1.actions[0],\n sourceNodeUuid: 'node-1',\n actionIndex: 0,\n mouseY: 150\n },\n bubbles: false\n });\n routerElement.dispatchEvent(dragOverEvent);\n await routerElement.updateComplete;\n\n // Verify external drag info was NOT stored (rejected due to wrong node type)\n const externalDragInfo = (routerElement as any).externalDragInfo;\n expect(externalDragInfo).to.be.null;\n });\n});\n"]}
@@ -0,0 +1,122 @@
1
+ import { expect, assert } from '@open-wc/testing';
2
+ import { CanvasMenu } from '../src/flow/CanvasMenu';
3
+ import { assertScreenshot, getClip, getComponent } from './utils.test';
4
+ describe('temba-canvas-menu', () => {
5
+ const createCanvasMenu = async () => {
6
+ const component = (await getComponent('temba-canvas-menu', {}, '', 250, 250));
7
+ await component.updateComplete;
8
+ return component;
9
+ };
10
+ it('can be created', async () => {
11
+ const menu = await createCanvasMenu();
12
+ assert.instanceOf(menu, CanvasMenu);
13
+ expect(menu.open).to.be.false;
14
+ });
15
+ it('is not visible when closed', async () => {
16
+ var _a;
17
+ const menu = await createCanvasMenu();
18
+ expect(menu.open).to.be.false;
19
+ // verify no menu is rendered
20
+ const menuElement = (_a = menu.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.menu');
21
+ expect(menuElement).to.be.null;
22
+ });
23
+ it('shows menu when opened', async () => {
24
+ var _a;
25
+ const menu = await createCanvasMenu();
26
+ // open the menu
27
+ menu.show(100, 100, { x: 50, y: 50 });
28
+ await menu.updateComplete;
29
+ expect(menu.open).to.be.true;
30
+ expect(menu.x).to.equal(100);
31
+ expect(menu.y).to.equal(100);
32
+ // verify menu is rendered
33
+ const menuElement = (_a = menu.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.menu');
34
+ expect(menuElement).to.not.be.null;
35
+ await assertScreenshot('canvas-menu/open', getClip(menu));
36
+ });
37
+ it('has three menu items', async () => {
38
+ var _a;
39
+ const menu = await createCanvasMenu();
40
+ menu.show(100, 100, { x: 50, y: 50 });
41
+ await menu.updateComplete;
42
+ const menuItems = (_a = menu.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.menu-item');
43
+ expect(menuItems === null || menuItems === void 0 ? void 0 : menuItems.length).to.equal(3);
44
+ // check menu item titles
45
+ const titles = Array.from(menuItems || []).map((item) => { var _a; return (_a = item.querySelector('.menu-item-title')) === null || _a === void 0 ? void 0 : _a.textContent; });
46
+ expect(titles).to.deep.equal([
47
+ 'Add Action',
48
+ 'Add Split',
49
+ 'Add Sticky Note'
50
+ ]);
51
+ });
52
+ it('closes when close() is called', async () => {
53
+ const menu = await createCanvasMenu();
54
+ menu.show(100, 100, { x: 50, y: 50 });
55
+ await menu.updateComplete;
56
+ expect(menu.open).to.be.true;
57
+ menu.close();
58
+ await menu.updateComplete;
59
+ expect(menu.open).to.be.false;
60
+ });
61
+ it('fires selection event when menu item is clicked', async () => {
62
+ var _a;
63
+ const menu = await createCanvasMenu();
64
+ menu.show(100, 100, { x: 50, y: 50 });
65
+ await menu.updateComplete;
66
+ let selectionFired = false;
67
+ let selectionDetail = null;
68
+ menu.addEventListener('temba-selection', (event) => {
69
+ selectionFired = true;
70
+ selectionDetail = event.detail;
71
+ });
72
+ // click on sticky note option (now the third item)
73
+ const menuItems = (_a = menu.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.menu-item');
74
+ const stickyItem = menuItems === null || menuItems === void 0 ? void 0 : menuItems[2];
75
+ stickyItem.click();
76
+ await menu.updateComplete;
77
+ expect(selectionFired).to.be.true;
78
+ expect(selectionDetail).to.deep.equal({
79
+ action: 'sticky',
80
+ position: { x: 50, y: 50 }
81
+ });
82
+ expect(menu.open).to.be.false;
83
+ });
84
+ it('adjusts position to stay within viewport bounds', async () => {
85
+ var _a, _b;
86
+ const menu = await createCanvasMenu();
87
+ // open menu at position that would go off screen
88
+ const viewportWidth = window.innerWidth;
89
+ const viewportHeight = window.innerHeight;
90
+ const margin = 10; // matches the margin in CanvasMenu
91
+ // position that would go off the right and bottom edges
92
+ menu.show(viewportWidth - 50, viewportHeight - 50, {
93
+ x: 100,
94
+ y: 100
95
+ });
96
+ await menu.updateComplete;
97
+ // wait for position adjustment
98
+ await new Promise((resolve) => setTimeout(resolve, 100));
99
+ await menu.updateComplete;
100
+ const menuElement = (_a = menu.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.menu');
101
+ expect(menuElement).to.not.be.null;
102
+ const menuRect = menuElement.getBoundingClientRect();
103
+ // verify menu stays within viewport with margin
104
+ expect(menuRect.right).to.be.at.most(viewportWidth - margin);
105
+ expect(menuRect.bottom).to.be.at.most(viewportHeight - margin);
106
+ // verify click position is preserved (not adjusted)
107
+ let selectionFired = false;
108
+ let selectionDetail = null;
109
+ menu.addEventListener('temba-selection', (event) => {
110
+ selectionFired = true;
111
+ selectionDetail = event.detail;
112
+ });
113
+ const menuItems = (_b = menu.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelectorAll('.menu-item');
114
+ const actionItem = menuItems === null || menuItems === void 0 ? void 0 : menuItems[0];
115
+ actionItem.click();
116
+ await menu.updateComplete;
117
+ expect(selectionFired).to.be.true;
118
+ // click position should remain unchanged
119
+ expect(selectionDetail.position).to.deep.equal({ x: 100, y: 100 });
120
+ });
121
+ });
122
+ //# sourceMappingURL=temba-canvas-menu.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temba-canvas-menu.test.js","sourceRoot":"","sources":["../../test/temba-canvas-menu.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, assert } from '@open-wc/testing';\nimport { CanvasMenu } from '../src/flow/CanvasMenu';\nimport { assertScreenshot, getClip, getComponent } from './utils.test';\n\ndescribe('temba-canvas-menu', () => {\n const createCanvasMenu = async () => {\n const component = (await getComponent(\n 'temba-canvas-menu',\n {},\n '',\n 250,\n 250\n )) as CanvasMenu;\n await component.updateComplete;\n return component;\n };\n\n it('can be created', async () => {\n const menu = await createCanvasMenu();\n assert.instanceOf(menu, CanvasMenu);\n expect(menu.open).to.be.false;\n });\n\n it('is not visible when closed', async () => {\n const menu = await createCanvasMenu();\n expect(menu.open).to.be.false;\n\n // verify no menu is rendered\n const menuElement = menu.shadowRoot?.querySelector('.menu');\n expect(menuElement).to.be.null;\n });\n\n it('shows menu when opened', async () => {\n const menu = await createCanvasMenu();\n\n // open the menu\n menu.show(100, 100, { x: 50, y: 50 });\n await menu.updateComplete;\n\n expect(menu.open).to.be.true;\n expect(menu.x).to.equal(100);\n expect(menu.y).to.equal(100);\n\n // verify menu is rendered\n const menuElement = menu.shadowRoot?.querySelector('.menu');\n expect(menuElement).to.not.be.null;\n\n await assertScreenshot('canvas-menu/open', getClip(menu));\n });\n\n it('has three menu items', async () => {\n const menu = await createCanvasMenu();\n menu.show(100, 100, { x: 50, y: 50 });\n await menu.updateComplete;\n\n const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');\n expect(menuItems?.length).to.equal(3);\n\n // check menu item titles\n const titles = Array.from(menuItems || []).map(\n (item) => item.querySelector('.menu-item-title')?.textContent\n );\n expect(titles).to.deep.equal([\n 'Add Action',\n 'Add Split',\n 'Add Sticky Note'\n ]);\n });\n\n it('closes when close() is called', async () => {\n const menu = await createCanvasMenu();\n menu.show(100, 100, { x: 50, y: 50 });\n await menu.updateComplete;\n\n expect(menu.open).to.be.true;\n\n menu.close();\n await menu.updateComplete;\n\n expect(menu.open).to.be.false;\n });\n\n it('fires selection event when menu item is clicked', async () => {\n const menu = await createCanvasMenu();\n menu.show(100, 100, { x: 50, y: 50 });\n await menu.updateComplete;\n\n let selectionFired = false;\n let selectionDetail = null;\n\n menu.addEventListener('temba-selection', (event: any) => {\n selectionFired = true;\n selectionDetail = event.detail;\n });\n\n // click on sticky note option (now the third item)\n const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');\n const stickyItem = menuItems?.[2] as HTMLElement;\n stickyItem.click();\n await menu.updateComplete;\n\n expect(selectionFired).to.be.true;\n expect(selectionDetail).to.deep.equal({\n action: 'sticky',\n position: { x: 50, y: 50 }\n });\n expect(menu.open).to.be.false;\n });\n\n it('adjusts position to stay within viewport bounds', async () => {\n const menu = await createCanvasMenu();\n\n // open menu at position that would go off screen\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n const margin = 10; // matches the margin in CanvasMenu\n\n // position that would go off the right and bottom edges\n menu.show(viewportWidth - 50, viewportHeight - 50, {\n x: 100,\n y: 100\n });\n await menu.updateComplete;\n\n // wait for position adjustment\n await new Promise((resolve) => setTimeout(resolve, 100));\n await menu.updateComplete;\n\n const menuElement = menu.shadowRoot?.querySelector('.menu') as HTMLElement;\n expect(menuElement).to.not.be.null;\n\n const menuRect = menuElement.getBoundingClientRect();\n\n // verify menu stays within viewport with margin\n expect(menuRect.right).to.be.at.most(viewportWidth - margin);\n expect(menuRect.bottom).to.be.at.most(viewportHeight - margin);\n\n // verify click position is preserved (not adjusted)\n let selectionFired = false;\n let selectionDetail = null;\n\n menu.addEventListener('temba-selection', (event: any) => {\n selectionFired = true;\n selectionDetail = event.detail;\n });\n\n const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');\n const actionItem = menuItems?.[0] as HTMLElement;\n actionItem.click();\n await menu.updateComplete;\n\n expect(selectionFired).to.be.true;\n // click position should remain unchanged\n expect(selectionDetail.position).to.deep.equal({ x: 100, y: 100 });\n });\n});\n"]}
@@ -3,6 +3,7 @@ import { html, fixture, expect } from '@open-wc/testing';
3
3
  import { CanvasNode } from '../src/flow/CanvasNode';
4
4
  import { stub, restore, useFakeTimers } from 'sinon';
5
5
  import { CustomEventType } from '../src/interfaces';
6
+ import { ACTION_GROUPS } from '../src/flow/types';
6
7
  describe('EditorNode', () => {
7
8
  let editorNode;
8
9
  let mockPlumber;
@@ -177,7 +178,7 @@ describe('EditorNode', () => {
177
178
  var _a;
178
179
  const config = {
179
180
  name: 'Test Action',
180
- color: '#ff0000'
181
+ group: ACTION_GROUPS.send // Uses 'send' group which has color '#3498db'
181
182
  };
182
183
  const mockAction = {
183
184
  type: 'send_msg',
@@ -189,7 +190,8 @@ describe('EditorNode', () => {
189
190
  expect(title).to.exist;
190
191
  const nameElement = title === null || title === void 0 ? void 0 : title.querySelector('.name');
191
192
  expect((_a = nameElement === null || nameElement === void 0 ? void 0 : nameElement.textContent) === null || _a === void 0 ? void 0 : _a.trim()).to.equal('Test Action');
192
- expect(title === null || title === void 0 ? void 0 : title.getAttribute('style')).to.contain('background:#ff0000');
193
+ // The 'send' group has color '#3498db' from ACTION_GROUP_METADATA
194
+ expect(title === null || title === void 0 ? void 0 : title.getAttribute('style')).to.contain('background:#3498db');
193
195
  });
194
196
  });
195
197
  describe('updated lifecycle', () => {
@@ -931,5 +933,86 @@ describe('EditorNode', () => {
931
933
  // This sequence ensures JSPlumb visuals stay in sync with the flow definition
932
934
  });
933
935
  });
936
+ describe('add action button', () => {
937
+ beforeEach(async () => {
938
+ editorNode = new CanvasNode();
939
+ editorNode['plumber'] = mockPlumber;
940
+ });
941
+ it('handleAddActionClick fires AddActionRequested event', () => {
942
+ const mockNode = {
943
+ uuid: 'test-node',
944
+ actions: [
945
+ {
946
+ type: 'send_msg',
947
+ uuid: 'action-1',
948
+ text: 'Hello',
949
+ quick_replies: []
950
+ }
951
+ ],
952
+ exits: [{ uuid: 'exit-1', destination_uuid: null }]
953
+ };
954
+ editorNode['node'] = mockNode;
955
+ let eventFired = false;
956
+ let eventDetail = null;
957
+ editorNode.addEventListener(CustomEventType.AddActionRequested, (e) => {
958
+ eventFired = true;
959
+ eventDetail = e.detail;
960
+ });
961
+ const mockEvent = {
962
+ preventDefault: stub(),
963
+ stopPropagation: stub()
964
+ };
965
+ // Call the add action click handler
966
+ editorNode.handleAddActionClick(mockEvent);
967
+ // Verify the event was fired with correct detail
968
+ expect(eventFired).to.be.true;
969
+ expect(eventDetail).to.exist;
970
+ expect(eventDetail.nodeUuid).to.equal('test-node');
971
+ // Verify event handlers were called
972
+ expect(mockEvent.preventDefault).to.have.been.called;
973
+ expect(mockEvent.stopPropagation).to.have.been.called;
974
+ });
975
+ it('renders add action button for execute_actions nodes', () => {
976
+ const mockNode = {
977
+ uuid: 'test-node',
978
+ actions: [
979
+ {
980
+ type: 'send_msg',
981
+ uuid: 'action-1',
982
+ text: 'Hello',
983
+ quick_replies: []
984
+ }
985
+ ],
986
+ exits: [{ uuid: 'exit-1', destination_uuid: null }]
987
+ };
988
+ const mockUI = {
989
+ position: { left: 0, top: 0 },
990
+ type: 'execute_actions'
991
+ };
992
+ editorNode['node'] = mockNode;
993
+ editorNode['ui'] = mockUI;
994
+ const rendered = editorNode.render();
995
+ expect(rendered).to.exist;
996
+ });
997
+ it('renders correctly for non-execute_actions nodes', () => {
998
+ const mockNode = {
999
+ uuid: 'test-node',
1000
+ actions: [],
1001
+ exits: [{ uuid: 'exit-1', destination_uuid: null }],
1002
+ router: {
1003
+ type: 'switch',
1004
+ categories: []
1005
+ }
1006
+ };
1007
+ const mockUI = {
1008
+ position: { left: 0, top: 0 },
1009
+ type: 'wait_for_response'
1010
+ };
1011
+ editorNode['node'] = mockNode;
1012
+ editorNode['ui'] = mockUI;
1013
+ const rendered = editorNode.render();
1014
+ expect(rendered).to.exist;
1015
+ });
1016
+ });
934
1017
  });
935
1018
  //# sourceMappingURL=temba-flow-editor-node.test.js.map