@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
@@ -1,6 +1,7 @@
1
1
  import { css, html, PropertyValueMap, TemplateResult } from 'lit';
2
2
  import { repeat } from 'lit/directives/repeat.js';
3
3
  import { ACTION_CONFIG, ActionConfig, NODE_CONFIG, NodeConfig } from './config';
4
+ import { ACTION_GROUP_METADATA, SPLIT_GROUP_METADATA } from './types';
4
5
  import { Action, Exit, Node, NodeUI, Router } from '../store/flow-definition';
5
6
  import { property } from 'lit/decorators.js';
6
7
  import { RapidElement } from '../RapidElement';
@@ -46,6 +47,18 @@ export class CanvasNode extends RapidElement {
46
47
  private nodeClickStartPos: { x: number; y: number } | null = null;
47
48
  private pendingNodeClick: { event: MouseEvent } | null = null;
48
49
 
50
+ // Track the height of the action being dragged (captured at drag start)
51
+ private draggedActionHeight: number = 0;
52
+
53
+ // Track external action drag (action being dragged from another node)
54
+ private externalDragInfo: {
55
+ action: Action;
56
+ sourceNodeUuid: string;
57
+ actionIndex: number;
58
+ dropIndex: number;
59
+ actionHeight: number;
60
+ } | null = null;
61
+
49
62
  static get styles() {
50
63
  return css`
51
64
 
@@ -160,7 +173,7 @@ export class CanvasNode extends RapidElement {
160
173
  pointer-events: auto; /* Ensure drag handle can receive events */
161
174
  }
162
175
  .title-spacer {
163
- width: 2em;
176
+ width: 1.8em;
164
177
 
165
178
  }
166
179
 
@@ -339,12 +352,79 @@ export class CanvasNode extends RapidElement {
339
352
  border-top-left-radius: var(--curvature);
340
353
  border-top-right-radius: var(--curvature);
341
354
  }
355
+
356
+ /* Add action button */
357
+ .add-action-button {
358
+ position: absolute;
359
+ bottom: 0.5em;
360
+ right: 0.5em;
361
+ width: 1.5em;
362
+ height: 1.5em;
363
+ border-radius: 50%;
364
+ background: var(--color-primary, #3b82f6);
365
+ color: white;
366
+ display: flex;
367
+ align-items: center;
368
+ justify-content: center;
369
+ cursor: pointer;
370
+ opacity: 0;
371
+ transition: opacity 200ms ease-in-out;
372
+ z-index: 10;
373
+ pointer-events: auto;
374
+ font-size: 0.9em;
375
+ }
376
+
377
+ .node.execute-actions:hover .add-action-button {
378
+ opacity: 0.8;
379
+ }
380
+
381
+ .add-action-button:hover {
382
+ opacity: 1 !important;
383
+ transform: scale(1.1);
384
+ }
342
385
  }`;
343
386
  }
344
387
 
345
388
  constructor() {
346
389
  super();
347
390
  this.handleActionOrderChanged = this.handleActionOrderChanged.bind(this);
391
+ this.handleActionDragStart = this.handleActionDragStart.bind(this);
392
+ this.handleActionDragExternal = this.handleActionDragExternal.bind(this);
393
+ this.handleActionDragInternal = this.handleActionDragInternal.bind(this);
394
+ this.handleActionDragStop = this.handleActionDragStop.bind(this);
395
+ this.handleExternalActionDragOver =
396
+ this.handleExternalActionDragOver.bind(this);
397
+ this.handleExternalActionDrop = this.handleExternalActionDrop.bind(this);
398
+ this.handleExternalActionDragLeave =
399
+ this.handleExternalActionDragLeave.bind(this);
400
+ this.handleActionShowGhost = this.handleActionShowGhost.bind(this);
401
+ this.handleActionHideGhost = this.handleActionHideGhost.bind(this);
402
+ }
403
+
404
+ connectedCallback() {
405
+ super.connectedCallback();
406
+
407
+ // Listen for external action drag events from Editor
408
+ this.addEventListener(
409
+ 'action-drag-over',
410
+ this.handleExternalActionDragOver as EventListener
411
+ );
412
+ this.addEventListener(
413
+ 'action-drop',
414
+ this.handleExternalActionDrop as EventListener
415
+ );
416
+ this.addEventListener(
417
+ 'action-drag-leave',
418
+ this.handleExternalActionDragLeave as EventListener
419
+ );
420
+ this.addEventListener(
421
+ 'action-show-ghost',
422
+ this.handleActionShowGhost as EventListener
423
+ );
424
+ this.addEventListener(
425
+ 'action-hide-ghost',
426
+ this.handleActionHideGhost as EventListener
427
+ );
348
428
  }
349
429
 
350
430
  protected updated(
@@ -391,6 +471,28 @@ export class CanvasNode extends RapidElement {
391
471
  // Remove the event listener when the component is removed
392
472
  super.disconnectedCallback();
393
473
 
474
+ // Remove external drag event listeners
475
+ this.removeEventListener(
476
+ 'action-drag-over',
477
+ this.handleExternalActionDragOver as EventListener
478
+ );
479
+ this.removeEventListener(
480
+ 'action-drop',
481
+ this.handleExternalActionDrop as EventListener
482
+ );
483
+ this.removeEventListener(
484
+ 'action-drag-leave',
485
+ this.handleExternalActionDragLeave as EventListener
486
+ );
487
+ this.removeEventListener(
488
+ 'action-show-ghost',
489
+ this.handleActionShowGhost as EventListener
490
+ );
491
+ this.removeEventListener(
492
+ 'action-hide-ghost',
493
+ this.handleActionHideGhost as EventListener
494
+ );
495
+
394
496
  // Clear any pending exit removal timeouts
395
497
  this.exitRemovalTimeouts.forEach((timeoutId) => {
396
498
  clearTimeout(timeoutId);
@@ -615,6 +717,82 @@ export class CanvasNode extends RapidElement {
615
717
  .updateNode(this.node.uuid, { ...this.node, actions: newActions });
616
718
  }
617
719
 
720
+ private handleActionDragStart(event: CustomEvent) {
721
+ // Capture the height of the action being dragged
722
+ const actionId = event.detail.id;
723
+ const actionElement = this.querySelector(`#${actionId}`) as HTMLElement;
724
+
725
+ if (actionElement) {
726
+ const rect = actionElement.getBoundingClientRect();
727
+ this.draggedActionHeight = rect.height;
728
+ } else {
729
+ // Fallback to a reasonable default
730
+ this.draggedActionHeight = 60;
731
+ }
732
+ }
733
+
734
+ private handleActionDragExternal(event: CustomEvent) {
735
+ // stop propagation of the original event from SortableList
736
+ event.stopPropagation();
737
+
738
+ // get the action being dragged
739
+ const actionId = event.detail.id;
740
+ const splitId = actionId.split('-');
741
+ if (splitId.length < 2 || isNaN(parseInt(splitId[1], 10))) {
742
+ // invalid format, do not proceed
743
+ return;
744
+ }
745
+ const actionIndex = parseInt(splitId[1], 10);
746
+ const action = this.node.actions[actionIndex];
747
+
748
+ // fire event to editor to show canvas drop preview, including the captured height
749
+ this.fireCustomEvent(CustomEventType.DragExternal, {
750
+ action,
751
+ nodeUuid: this.node.uuid,
752
+ actionIndex,
753
+ mouseX: event.detail.mouseX,
754
+ mouseY: event.detail.mouseY,
755
+ actionHeight: this.draggedActionHeight
756
+ });
757
+ }
758
+
759
+ private handleActionDragInternal(_event: CustomEvent) {
760
+ // stop propagation of the original event from SortableList
761
+ _event.stopPropagation();
762
+
763
+ // fire event to editor to hide canvas drop preview
764
+ this.fireCustomEvent(CustomEventType.DragInternal, {});
765
+ }
766
+
767
+ private handleActionDragStop(event: CustomEvent) {
768
+ const isExternal = event.detail.isExternal;
769
+
770
+ if (isExternal) {
771
+ // stop propagation of the original event from SortableList
772
+ event.stopPropagation();
773
+
774
+ // get the action being dragged
775
+ const actionId = event.detail.id;
776
+ const split = actionId.split('-');
777
+ if (split.length < 2 || isNaN(Number(split[1]))) {
778
+ // invalid actionId format, do not proceed
779
+ return;
780
+ }
781
+ const actionIndex = parseInt(split[1], 10);
782
+ const action = this.node.actions[actionIndex];
783
+
784
+ // fire event to editor to create new node
785
+ this.fireCustomEvent(CustomEventType.DragStop, {
786
+ action,
787
+ nodeUuid: this.node.uuid,
788
+ actionIndex,
789
+ isExternal: true,
790
+ mouseX: event.detail.mouseX,
791
+ mouseY: event.detail.mouseY
792
+ });
793
+ }
794
+ }
795
+
618
796
  private handleActionMouseDown(event: MouseEvent, action: Action): void {
619
797
  // Don't handle clicks on the remove button, drag handle, or when action is in removing state
620
798
  const target = event.target as HTMLElement;
@@ -813,16 +991,171 @@ export class CanvasNode extends RapidElement {
813
991
  this.pendingNodeClick = null;
814
992
  }
815
993
 
994
+ private handleAddActionClick(event: MouseEvent): void {
995
+ event.preventDefault();
996
+ event.stopPropagation();
997
+
998
+ // Fire event to request adding a new action to this node
999
+ this.fireCustomEvent(CustomEventType.AddActionRequested, {
1000
+ nodeUuid: this.node.uuid
1001
+ });
1002
+ }
1003
+
1004
+ private calculateDropIndex(mouseY: number): number {
1005
+ // Get the sortable list element
1006
+ const sortableList = this.querySelector('temba-sortable-list');
1007
+ if (!sortableList || !this.node.actions)
1008
+ return this.node.actions?.length ?? 0;
1009
+
1010
+ // Get all action elements
1011
+ const actionElements = Array.from(
1012
+ sortableList.querySelectorAll('.action.sortable')
1013
+ );
1014
+
1015
+ if (actionElements.length === 0) {
1016
+ return 0;
1017
+ }
1018
+
1019
+ // Find where to insert based on mouse Y position
1020
+ for (let i = 0; i < actionElements.length; i++) {
1021
+ const actionElement = actionElements[i] as HTMLElement;
1022
+ const rect = actionElement.getBoundingClientRect();
1023
+ const centerY = rect.top + rect.height / 2;
1024
+
1025
+ if (mouseY < centerY) {
1026
+ return i;
1027
+ }
1028
+ }
1029
+
1030
+ // If past all elements, insert at the end
1031
+ return actionElements.length;
1032
+ }
1033
+
1034
+ private handleExternalActionDragOver(event: CustomEvent): void {
1035
+ // Only handle if this is an execute_actions node
1036
+ if (this.ui.type !== 'execute_actions') return;
1037
+
1038
+ const { action, sourceNodeUuid, actionIndex, mouseY, actionHeight } =
1039
+ event.detail;
1040
+
1041
+ // Don't accept drops from the same node
1042
+ if (sourceNodeUuid === this.node.uuid) return;
1043
+
1044
+ // Calculate where to drop
1045
+ const dropIndex = this.calculateDropIndex(mouseY);
1046
+
1047
+ // Store the drag info
1048
+ this.externalDragInfo = {
1049
+ action,
1050
+ sourceNodeUuid,
1051
+ actionIndex,
1052
+ dropIndex,
1053
+ actionHeight: actionHeight || 60 // fallback to 60px if not provided
1054
+ };
1055
+
1056
+ // Request update to show placeholder
1057
+ this.requestUpdate();
1058
+ }
1059
+
1060
+ private handleExternalActionDragLeave(_event: CustomEvent): void {
1061
+ // Clear external drag state when drag leaves this node
1062
+ this.externalDragInfo = null;
1063
+ this.requestUpdate();
1064
+ }
1065
+
1066
+ private handleActionShowGhost(_event: CustomEvent): void {
1067
+ // Show the ghost element in the sortable list
1068
+ const sortableList = this.querySelector('temba-sortable-list');
1069
+ if (sortableList) {
1070
+ const ghostElement = document.querySelector('.ghost') as HTMLElement;
1071
+ if (ghostElement) {
1072
+ ghostElement.style.display = 'block';
1073
+ }
1074
+ }
1075
+ }
1076
+
1077
+ private handleActionHideGhost(_event: CustomEvent): void {
1078
+ // Hide the ghost element in the sortable list
1079
+ const sortableList = this.querySelector('temba-sortable-list');
1080
+ if (sortableList) {
1081
+ const ghostElement = document.querySelector('.ghost') as HTMLElement;
1082
+ if (ghostElement) {
1083
+ ghostElement.style.display = 'none';
1084
+ }
1085
+ }
1086
+ }
1087
+
1088
+ private handleExternalActionDrop(event: CustomEvent): void {
1089
+ // Only handle if this is an execute_actions node
1090
+ if (this.ui.type !== 'execute_actions') return;
1091
+
1092
+ const { action, sourceNodeUuid, actionIndex } = event.detail;
1093
+
1094
+ // Don't accept drops from the same node
1095
+ if (sourceNodeUuid === this.node.uuid) return;
1096
+
1097
+ // Get the drop index from our tracking state
1098
+ const dropIndex =
1099
+ this.externalDragInfo?.dropIndex ?? this.node.actions?.length ?? 0;
1100
+
1101
+ // Clear external drag state
1102
+ this.externalDragInfo = null;
1103
+
1104
+ // Remove the action from the source node
1105
+ const store = getStore();
1106
+ if (!store) return;
1107
+
1108
+ const flowDefinition = store.getState().flowDefinition;
1109
+ if (!flowDefinition) return;
1110
+
1111
+ const sourceNode = flowDefinition.nodes.find(
1112
+ (n) => n.uuid === sourceNodeUuid
1113
+ );
1114
+
1115
+ if (sourceNode) {
1116
+ const updatedSourceActions = sourceNode.actions.filter(
1117
+ (_a, idx) => idx !== actionIndex
1118
+ );
1119
+
1120
+ // If source node has no actions left, remove it
1121
+ if (updatedSourceActions.length === 0) {
1122
+ this.fireCustomEvent(CustomEventType.NodeDeleted, {
1123
+ uuid: sourceNodeUuid
1124
+ });
1125
+ } else {
1126
+ // Update source node
1127
+ const updatedSourceNode = {
1128
+ ...sourceNode,
1129
+ actions: updatedSourceActions
1130
+ };
1131
+ getStore()?.getState().updateNode(sourceNodeUuid, updatedSourceNode);
1132
+ }
1133
+ }
1134
+
1135
+ // Add the action to this node at the calculated position
1136
+ const newActions = [...this.node.actions];
1137
+ newActions.splice(dropIndex, 0, action);
1138
+
1139
+ const updatedNode = { ...this.node, actions: newActions };
1140
+ getStore()?.getState().updateNode(this.node.uuid, updatedNode);
1141
+
1142
+ // Request update
1143
+ this.requestUpdate();
1144
+ }
1145
+
816
1146
  private renderTitle(
817
1147
  config: ActionConfig,
818
1148
  action: Action,
819
1149
  index: number,
820
1150
  isRemoving: boolean = false
821
1151
  ) {
822
- return html`<div class="cn-title" style="background:${config.color}">
1152
+ const color = config.group
1153
+ ? ACTION_GROUP_METADATA[config.group]?.color
1154
+ : '#aaaaaa';
1155
+ return html`<div class="cn-title" style="background:${color}">
823
1156
  ${this.node?.actions?.length > 1
824
1157
  ? html`<temba-icon class="drag-handle" name="sort"></temba-icon>`
825
- : null}
1158
+ : html`<div class="title-spacer"></div>`}
826
1159
 
827
1160
  <div class="name">${isRemoving ? 'Remove?' : config.name}</div>
828
1161
  <div
@@ -836,13 +1169,29 @@ export class CanvasNode extends RapidElement {
836
1169
  </div>`;
837
1170
  }
838
1171
 
839
- private renderNodeTitle(config: NodeConfig, isRemoving: boolean = false) {
1172
+ private renderNodeTitle(
1173
+ config: NodeConfig,
1174
+ node: Node,
1175
+ ui: NodeUI,
1176
+ isRemoving: boolean = false
1177
+ ) {
1178
+ // Get color from the appropriate metadata (either ACTION or SPLIT)
1179
+ const color = config.group
1180
+ ? ACTION_GROUP_METADATA[config.group]?.color ||
1181
+ SPLIT_GROUP_METADATA[config.group]?.color
1182
+ : '#aaaaaa';
840
1183
  return html`<div
841
1184
  class="cn-title ${isRemoving ? 'removing' : ''}"
842
- style="background:${config.color}"
1185
+ style="background:${color}"
843
1186
  >
844
1187
  <div class="title-spacer"></div>
845
- <div class="name">${isRemoving ? 'Remove?' : config.name}</div>
1188
+ <div class="name">
1189
+ ${isRemoving
1190
+ ? 'Remove?'
1191
+ : config.renderTitle
1192
+ ? config.renderTitle(node, ui)
1193
+ : html`${config.name}`}
1194
+ </div>
846
1195
  <div
847
1196
  class="remove-button"
848
1197
  @click=${(e: MouseEvent) => this.handleNodeRemoveClick(e)}
@@ -853,6 +1202,14 @@ export class CanvasNode extends RapidElement {
853
1202
  </div>`;
854
1203
  }
855
1204
 
1205
+ private renderDropPlaceholder() {
1206
+ const height = this.externalDragInfo?.actionHeight || 60;
1207
+ return html`<div
1208
+ class="action sortable drop-placeholder"
1209
+ style="height: ${height}px; background: #f3f4f6; border: 2px dashed #d1d5db; border-radius: var(--curvature);"
1210
+ ></div>`;
1211
+ }
1212
+
856
1213
  private renderAction(node: Node, action: Action, index: number) {
857
1214
  const config = ACTION_CONFIG[action.type];
858
1215
  const isRemoving = this.actionRemovingState.has(action.uuid);
@@ -893,6 +1250,31 @@ export class CanvasNode extends RapidElement {
893
1250
  </div>`;
894
1251
  }
895
1252
 
1253
+ private renderActionsWithPlaceholder() {
1254
+ if (!this.externalDragInfo) {
1255
+ // No external drag, render normally
1256
+ return this.node.actions.map((action, index) =>
1257
+ this.renderAction(this.node, action, index)
1258
+ );
1259
+ }
1260
+
1261
+ // Insert placeholder at the drop index
1262
+ const result = [];
1263
+ for (let i = 0; i < this.node.actions.length; i++) {
1264
+ if (i === this.externalDragInfo.dropIndex) {
1265
+ result.push(this.renderDropPlaceholder());
1266
+ }
1267
+ result.push(this.renderAction(this.node, this.node.actions[i], i));
1268
+ }
1269
+
1270
+ // If dropping at the end, add placeholder after all actions
1271
+ if (this.externalDragInfo.dropIndex >= this.node.actions.length) {
1272
+ result.push(this.renderDropPlaceholder());
1273
+ }
1274
+
1275
+ return result;
1276
+ }
1277
+
896
1278
  private renderRouter(router: Router, ui: NodeUI) {
897
1279
  const nodeConfig = NODE_CONFIG[ui.type];
898
1280
  if (nodeConfig) {
@@ -978,20 +1360,27 @@ export class CanvasNode extends RapidElement {
978
1360
  >
979
1361
  ${this.renderNodeTitle(
980
1362
  nodeConfig,
1363
+ this.node,
1364
+ this.ui,
981
1365
  this.actionRemovingState.has(this.node.uuid)
982
1366
  )}
983
- ${nodeConfig.render ? nodeConfig.render(this.node) : null}
1367
+ ${nodeConfig.render
1368
+ ? nodeConfig.render(this.node, this.ui)
1369
+ : null}
984
1370
  </div>
985
1371
  </div>`
986
1372
  : this.node.actions.length > 0
987
1373
  ? this.ui.type === 'execute_actions'
988
1374
  ? html`<temba-sortable-list
989
1375
  dragHandle="drag-handle"
1376
+ externalDrag
990
1377
  @temba-order-changed="${this.handleActionOrderChanged}"
1378
+ @temba-drag-start="${this.handleActionDragStart}"
1379
+ @temba-drag-external="${this.handleActionDragExternal}"
1380
+ @temba-drag-internal="${this.handleActionDragInternal}"
1381
+ @temba-drag-stop="${this.handleActionDragStop}"
991
1382
  >
992
- ${this.node.actions.map((action, index) =>
993
- this.renderAction(this.node, action, index)
994
- )}
1383
+ ${this.renderActionsWithPlaceholder()}
995
1384
  </temba-sortable-list>`
996
1385
  : html`${this.node.actions.map((action, index) =>
997
1386
  this.renderAction(this.node, action, index)
@@ -1007,6 +1396,15 @@ export class CanvasNode extends RapidElement {
1007
1396
  (exit) => this.renderExit(exit)
1008
1397
  )}
1009
1398
  </div>`}
1399
+ ${this.ui.type === 'execute_actions'
1400
+ ? html`<div
1401
+ class="add-action-button"
1402
+ @click=${(e: MouseEvent) => this.handleAddActionClick(e)}
1403
+ title="Add action"
1404
+ >
1405
+ <temba-icon name="add"></temba-icon>
1406
+ </div>`
1407
+ : ''}
1010
1408
  </div>
1011
1409
  `;
1012
1410
  }