@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
@@ -7,6 +7,9 @@ import { fromStore, zustand } from '../store/AppState';
7
7
  import { RapidElement } from '../RapidElement';
8
8
  import { repeat } from 'lit-html/directives/repeat.js';
9
9
  import { CustomEventType } from '../interfaces';
10
+ import { generateUUID } from '../utils';
11
+ import { ACTION_CONFIG, NODE_CONFIG } from './config';
12
+ import { ACTION_GROUP_METADATA } from './types';
10
13
  import { Plumber } from './Plumber';
11
14
  import { CanvasNode } from './CanvasNode';
12
15
  export function snapToGrid(value) {
@@ -24,6 +27,10 @@ export function findNodeForExit(definition, exitUuid) {
24
27
  }
25
28
  const SAVE_QUIET_TIME = 500;
26
29
  const DRAG_THRESHOLD = 5;
30
+ // Offset for positioning dropped action node relative to mouse cursor
31
+ // Keep small to make drop location close to cursor position
32
+ const DROP_PREVIEW_OFFSET_X = 20;
33
+ const DROP_PREVIEW_OFFSET_Y = 20;
27
34
  export class Editor extends RapidElement {
28
35
  // unfortunately, jsplumb requires that we be in light DOM
29
36
  createRenderRoot() {
@@ -200,13 +207,22 @@ export class Editor extends RapidElement {
200
207
  this.editingNode = null;
201
208
  this.editingNodeUI = null;
202
209
  this.editingAction = null;
210
+ this.isCreatingNewNode = false;
211
+ this.pendingNodePosition = null;
212
+ // Canvas drop state for dragging actions to canvas
213
+ this.canvasDropPreview = null;
214
+ this.addActionToNodeUuid = null;
215
+ // Track target node for action drag
216
+ this.actionDragTargetNodeUuid = null;
217
+ // Track previous target node to clear placeholder when moving between nodes
218
+ this.previousActionDragTargetNodeUuid = null;
203
219
  this.canvasMouseDown = false;
204
220
  // Bound event handlers to maintain proper 'this' context
205
221
  this.boundMouseMove = this.handleMouseMove.bind(this);
206
222
  this.boundMouseUp = this.handleMouseUp.bind(this);
207
223
  this.boundGlobalMouseDown = this.handleGlobalMouseDown.bind(this);
208
224
  this.boundKeyDown = this.handleKeyDown.bind(this);
209
- this.boundCanvasDoubleClick = this.handleCanvasDoubleClick.bind(this);
225
+ this.boundCanvasContextMenu = this.handleCanvasContextMenu.bind(this);
210
226
  }
211
227
  firstUpdated(changes) {
212
228
  super.firstUpdated(changes);
@@ -305,7 +321,7 @@ export class Editor extends RapidElement {
305
321
  document.removeEventListener('keydown', this.boundKeyDown);
306
322
  const canvas = this.querySelector('#canvas');
307
323
  if (canvas) {
308
- canvas.removeEventListener('dblclick', this.boundCanvasDoubleClick);
324
+ canvas.removeEventListener('contextmenu', this.boundCanvasContextMenu);
309
325
  }
310
326
  }
311
327
  setupGlobalEventListeners() {
@@ -315,20 +331,40 @@ export class Editor extends RapidElement {
315
331
  document.addEventListener('keydown', this.boundKeyDown);
316
332
  const canvas = this.querySelector('#canvas');
317
333
  if (canvas) {
318
- canvas.addEventListener('dblclick', this.boundCanvasDoubleClick);
334
+ canvas.addEventListener('contextmenu', this.boundCanvasContextMenu);
319
335
  }
320
336
  // Listen for action edit requests from flow nodes
321
337
  this.addEventListener(CustomEventType.ActionEditRequested, this.handleActionEditRequested.bind(this));
338
+ // Listen for add action requests from flow nodes
339
+ this.addEventListener(CustomEventType.AddActionRequested, this.handleAddActionRequested.bind(this));
322
340
  // Listen for node edit requests from flow nodes
323
341
  this.addEventListener(CustomEventType.NodeEditRequested, this.handleNodeEditRequested.bind(this));
342
+ // Listen for canvas menu selections
343
+ this.addEventListener(CustomEventType.Selection, (event) => {
344
+ const target = event.target;
345
+ if (target.tagName === 'TEMBA-CANVAS-MENU') {
346
+ this.handleCanvasMenuSelection(event);
347
+ }
348
+ else if (target.tagName === 'TEMBA-NODE-TYPE-SELECTOR') {
349
+ this.handleNodeTypeSelection(event);
350
+ }
351
+ });
352
+ // Listen for action drag events from nodes
353
+ this.addEventListener(CustomEventType.DragExternal, this.handleActionDragExternal.bind(this));
354
+ this.addEventListener(CustomEventType.DragInternal, this.handleActionDragInternal.bind(this));
355
+ this.addEventListener(CustomEventType.DragStop, (event) => {
356
+ if (event.detail.isExternal) {
357
+ this.handleActionDropExternal(event);
358
+ }
359
+ });
324
360
  }
325
361
  getPosition(uuid, type) {
326
- var _a, _b, _c;
362
+ var _b, _c, _d;
327
363
  if (type === 'node') {
328
- return (_a = this.definition._ui.nodes[uuid]) === null || _a === void 0 ? void 0 : _a.position;
364
+ return (_b = this.definition._ui.nodes[uuid]) === null || _b === void 0 ? void 0 : _b.position;
329
365
  }
330
366
  else {
331
- return (_c = (_b = this.definition._ui.stickies) === null || _b === void 0 ? void 0 : _b[uuid]) === null || _c === void 0 ? void 0 : _c.position;
367
+ return (_d = (_c = this.definition._ui.stickies) === null || _c === void 0 ? void 0 : _c[uuid]) === null || _d === void 0 ? void 0 : _d.position;
332
368
  }
333
369
  }
334
370
  handleMouseDown(event) {
@@ -370,12 +406,12 @@ export class Editor extends RapidElement {
370
406
  event.stopPropagation();
371
407
  }
372
408
  handleGlobalMouseDown(event) {
373
- var _a;
409
+ var _b;
374
410
  // ignore right clicks
375
411
  if (event.button !== 0)
376
412
  return;
377
413
  // Check if the click is within our canvas
378
- const canvasRect = (_a = this.querySelector('#grid')) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
414
+ const canvasRect = (_b = this.querySelector('#grid')) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
379
415
  if (!canvasRect)
380
416
  return;
381
417
  const isWithinCanvas = event.clientX >= canvasRect.left &&
@@ -395,14 +431,14 @@ export class Editor extends RapidElement {
395
431
  this.handleCanvasMouseDown(event);
396
432
  }
397
433
  handleCanvasMouseDown(event) {
398
- var _a;
434
+ var _b;
399
435
  const target = event.target;
400
436
  if (target.id === 'canvas' || target.id === 'grid') {
401
437
  // Ignore clicks on exits
402
438
  // Start selection box
403
439
  this.canvasMouseDown = true;
404
440
  this.dragStartPos = { x: event.clientX, y: event.clientY };
405
- const canvasRect = (_a = this.querySelector('#canvas')) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
441
+ const canvasRect = (_b = this.querySelector('#canvas')) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
406
442
  if (canvasRect) {
407
443
  // Clear current selection
408
444
  this.selectedItems.clear();
@@ -466,16 +502,16 @@ export class Editor extends RapidElement {
466
502
  deleteSelectedItems() {
467
503
  const nodes = Array.from(this.selectedItems).filter((uuid) => this.definition.nodes.some((node) => node.uuid === uuid));
468
504
  this.deleteNodes(Array.from(nodes));
469
- const stickies = Array.from(this.selectedItems).filter((uuid) => { var _a, _b; return (_b = (_a = this.definition._ui) === null || _a === void 0 ? void 0 : _a.stickies) === null || _b === void 0 ? void 0 : _b[uuid]; });
505
+ const stickies = Array.from(this.selectedItems).filter((uuid) => { var _b, _c; return (_c = (_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.stickies) === null || _c === void 0 ? void 0 : _c[uuid]; });
470
506
  getStore().getState().removeStickyNotes(stickies);
471
507
  // Clear selection
472
508
  this.selectedItems.clear();
473
509
  }
474
510
  updateSelectionBox(event) {
475
- var _a;
511
+ var _b;
476
512
  if (!this.selectionBox || !this.canvasMouseDown)
477
513
  return;
478
- const canvasRect = (_a = this.querySelector('#canvas')) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
514
+ const canvasRect = (_b = this.querySelector('#canvas')) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
479
515
  if (!canvasRect)
480
516
  return;
481
517
  const relativeX = event.clientX - canvasRect.left;
@@ -489,7 +525,7 @@ export class Editor extends RapidElement {
489
525
  this.updateSelectedItemsFromBox();
490
526
  }
491
527
  updateSelectedItemsFromBox() {
492
- var _a, _b, _c;
528
+ var _b, _c, _d;
493
529
  if (!this.selectionBox)
494
530
  return;
495
531
  const newSelection = new Set();
@@ -498,14 +534,14 @@ export class Editor extends RapidElement {
498
534
  const boxRight = Math.max(this.selectionBox.startX, this.selectionBox.endX);
499
535
  const boxBottom = Math.max(this.selectionBox.startY, this.selectionBox.endY);
500
536
  // Check nodes
501
- (_a = this.definition) === null || _a === void 0 ? void 0 : _a.nodes.forEach((node) => {
502
- var _a, _b, _c;
537
+ (_b = this.definition) === null || _b === void 0 ? void 0 : _b.nodes.forEach((node) => {
538
+ var _b, _c, _d;
503
539
  const nodeElement = this.querySelector(`[id="${node.uuid}"]`);
504
540
  if (nodeElement) {
505
- const position = (_b = (_a = this.definition._ui) === null || _a === void 0 ? void 0 : _a.nodes[node.uuid]) === null || _b === void 0 ? void 0 : _b.position;
541
+ const position = (_c = (_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.nodes[node.uuid]) === null || _c === void 0 ? void 0 : _c.position;
506
542
  if (position) {
507
543
  const rect = nodeElement.getBoundingClientRect();
508
- const canvasRect = (_c = this.querySelector('#canvas')) === null || _c === void 0 ? void 0 : _c.getBoundingClientRect();
544
+ const canvasRect = (_d = this.querySelector('#canvas')) === null || _d === void 0 ? void 0 : _d.getBoundingClientRect();
509
545
  if (canvasRect) {
510
546
  const nodeLeft = position.left;
511
547
  const nodeTop = position.top;
@@ -523,7 +559,7 @@ export class Editor extends RapidElement {
523
559
  }
524
560
  });
525
561
  // Check sticky notes
526
- const stickies = ((_c = (_b = this.definition) === null || _b === void 0 ? void 0 : _b._ui) === null || _c === void 0 ? void 0 : _c.stickies) || {};
562
+ const stickies = ((_d = (_c = this.definition) === null || _c === void 0 ? void 0 : _c._ui) === null || _d === void 0 ? void 0 : _d.stickies) || {};
527
563
  Object.entries(stickies).forEach(([uuid, sticky]) => {
528
564
  if (sticky.position) {
529
565
  const stickyElement = this.querySelector(`temba-sticky-note[uuid="${uuid}"]`);
@@ -561,6 +597,49 @@ export class Editor extends RapidElement {
561
597
  style="left: ${left}px; top: ${top}px; width: ${width}px; height: ${height}px;"
562
598
  ></div>`;
563
599
  }
600
+ renderCanvasDropPreview() {
601
+ var _b;
602
+ if (!this.canvasDropPreview)
603
+ return '';
604
+ const { action, position } = this.canvasDropPreview;
605
+ const actionConfig = ACTION_CONFIG[action.type];
606
+ if (!actionConfig)
607
+ return '';
608
+ return html `<div
609
+ class="canvas-drop-preview"
610
+ style="position: absolute; left: ${position.left}px; top: ${position.top}px; opacity: 0.6; pointer-events: none; z-index: 10000;"
611
+ >
612
+ <div
613
+ class="node execute-actions"
614
+ style="outline: 3px dashed var(--color-primary, #3b82f6); outline-offset: 2px; border-radius: var(--curvature);"
615
+ >
616
+ <div class="action sortable ${action.type}">
617
+ <div class="action-content">
618
+ <div
619
+ class="cn-title"
620
+ style="background: ${actionConfig.group
621
+ ? (_b = ACTION_GROUP_METADATA[actionConfig.group]) === null || _b === void 0 ? void 0 : _b.color
622
+ : '#aaaaaa'}"
623
+ >
624
+ <div class="title-spacer"></div>
625
+ <div class="name">${actionConfig.name}</div>
626
+ <div class="title-spacer"></div>
627
+ </div>
628
+ <div class="body">
629
+ ${actionConfig.render
630
+ ? actionConfig.render({ actions: [action] }, action)
631
+ : html `<pre>${action.type}</pre>`}
632
+ </div>
633
+ </div>
634
+ </div>
635
+ <div class="action-exits">
636
+ <div class="exit-wrapper">
637
+ <div class="exit"></div>
638
+ </div>
639
+ </div>
640
+ </div>
641
+ </div>`;
642
+ }
564
643
  handleMouseMove(event) {
565
644
  // Handle selection box drawing
566
645
  if (this.canvasMouseDown && !this.isMouseDown) {
@@ -693,7 +772,7 @@ export class Editor extends RapidElement {
693
772
  this.canvasMouseDown = false;
694
773
  }
695
774
  updateCanvasSize() {
696
- var _a;
775
+ var _b;
697
776
  if (!this.definition)
698
777
  return;
699
778
  const store = getStore();
@@ -715,7 +794,7 @@ export class Editor extends RapidElement {
715
794
  }
716
795
  });
717
796
  // Check sticky note positions
718
- const stickies = ((_a = this.definition._ui) === null || _a === void 0 ? void 0 : _a.stickies) || {};
797
+ const stickies = ((_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.stickies) || {};
719
798
  Object.entries(stickies).forEach(([uuid, sticky]) => {
720
799
  if (sticky.position) {
721
800
  const stickyElement = this.querySelector(`temba-sticky-note[uuid="${uuid}"]`);
@@ -738,12 +817,15 @@ export class Editor extends RapidElement {
738
817
  // Update canvas size in store
739
818
  store.getState().expandCanvas(maxWidth, maxHeight);
740
819
  }
741
- handleCanvasDoubleClick(event) {
742
- // Check if we double-clicked on empty canvas space
820
+ handleCanvasContextMenu(event) {
821
+ // Check if we right-clicked on empty canvas space
743
822
  const target = event.target;
744
823
  if (target.id !== 'canvas') {
745
824
  return;
746
825
  }
826
+ // Prevent the default browser context menu
827
+ event.preventDefault();
828
+ event.stopPropagation();
747
829
  // Get canvas position
748
830
  const canvas = this.querySelector('#canvas');
749
831
  if (!canvas) {
@@ -755,14 +837,119 @@ export class Editor extends RapidElement {
755
837
  // Snap position to grid
756
838
  const snappedLeft = snapToGrid(relativeX);
757
839
  const snappedTop = snapToGrid(relativeY);
758
- // Create new sticky note
840
+ // Show the canvas menu at the mouse position (use viewport coordinates)
841
+ const canvasMenu = this.querySelector('temba-canvas-menu');
842
+ if (canvasMenu) {
843
+ canvasMenu.show(event.clientX, event.clientY, {
844
+ x: snappedLeft,
845
+ y: snappedTop
846
+ });
847
+ }
848
+ }
849
+ handleCanvasMenuSelection(event) {
850
+ const selection = event.detail;
759
851
  const store = getStore();
760
- store.getState().createStickyNote({
761
- left: snappedLeft,
762
- top: snappedTop
763
- });
764
- event.preventDefault();
765
- event.stopPropagation();
852
+ if (selection.action === 'sticky') {
853
+ // Create new sticky note
854
+ store.getState().createStickyNote({
855
+ left: selection.position.x,
856
+ top: selection.position.y
857
+ });
858
+ }
859
+ else {
860
+ // Show node type selector
861
+ const selector = this.querySelector('temba-node-type-selector');
862
+ if (selector) {
863
+ selector.show(selection.action, selection.position);
864
+ }
865
+ }
866
+ }
867
+ handleNodeTypeSelection(event) {
868
+ const selection = event.detail;
869
+ // Check if we're adding an action to an existing node
870
+ if (this.addActionToNodeUuid) {
871
+ // Find the existing node
872
+ const node = this.definition.nodes.find((n) => n.uuid === this.addActionToNodeUuid);
873
+ const nodeUI = this.definition._ui.nodes[this.addActionToNodeUuid];
874
+ if (node && nodeUI) {
875
+ // Create a new action to add to the existing node
876
+ const actionUuid = generateUUID();
877
+ this.editingAction = {
878
+ uuid: actionUuid,
879
+ type: selection.nodeType
880
+ };
881
+ // Set the editing node to the existing node (not creating new)
882
+ this.editingNode = node;
883
+ this.editingNodeUI = nodeUI;
884
+ this.isCreatingNewNode = false;
885
+ // Clear the addActionToNodeUuid flag
886
+ this.addActionToNodeUuid = null;
887
+ return;
888
+ }
889
+ // If we couldn't find the node, clear the flag and continue with normal flow
890
+ this.addActionToNodeUuid = null;
891
+ }
892
+ // Create a temporary node structure for editing (not added to store yet)
893
+ const nodeUuid = generateUUID();
894
+ // Determine if this is an action type or a node type
895
+ // Actions need to be wrapped in an execute_actions node
896
+ const isActionType = selection.nodeType in ACTION_CONFIG;
897
+ const nodeType = isActionType ? 'execute_actions' : selection.nodeType;
898
+ // For nodes with routers, initialize an empty router to ensure fromFormData works correctly
899
+ const nodeConfig = NODE_CONFIG[nodeType];
900
+ const hasRouter = (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.form) &&
901
+ Object.keys(nodeConfig.form).some((key) => {
902
+ var _b;
903
+ return ['rules', 'categories', 'cases'].includes(key) ||
904
+ ((_b = nodeConfig.form[key]) === null || _b === void 0 ? void 0 : _b.type) === 'array';
905
+ });
906
+ const tempNode = {
907
+ uuid: nodeUuid,
908
+ actions: [],
909
+ exits: hasRouter
910
+ ? [] // Router-based nodes will generate their own exits
911
+ : [
912
+ {
913
+ uuid: generateUUID(),
914
+ destination_uuid: null
915
+ }
916
+ ]
917
+ };
918
+ if (hasRouter) {
919
+ // This node uses a router - initialize it with empty structure
920
+ tempNode.router = {
921
+ type: 'switch',
922
+ categories: [],
923
+ cases: [],
924
+ operand: '@input.text',
925
+ default_category_uuid: undefined
926
+ };
927
+ }
928
+ const tempNodeUI = {
929
+ position: {
930
+ left: selection.position.x,
931
+ top: selection.position.y
932
+ },
933
+ type: nodeType,
934
+ config: {}
935
+ };
936
+ // Mark that we're creating a new node and store the position
937
+ this.isCreatingNewNode = true;
938
+ this.pendingNodePosition = {
939
+ left: selection.position.x,
940
+ top: selection.position.y
941
+ };
942
+ // Open the node editor with the temporary node
943
+ this.editingNode = tempNode;
944
+ this.editingNodeUI = tempNodeUI;
945
+ // If this is an action type, we also need to set up an editing action
946
+ if (isActionType) {
947
+ const actionUuid = generateUUID();
948
+ this.editingAction = {
949
+ uuid: actionUuid,
950
+ type: selection.nodeType
951
+ };
952
+ }
766
953
  }
767
954
  handleActionEditRequested(event) {
768
955
  // For action editing, we set the action and find the corresponding node
@@ -775,24 +962,80 @@ export class Editor extends RapidElement {
775
962
  this.editingNodeUI = this.definition._ui.nodes[nodeUuid];
776
963
  }
777
964
  }
965
+ handleAddActionRequested(event) {
966
+ // Get the node where we want to add the action
967
+ const nodeUuid = event.detail.nodeUuid;
968
+ const node = this.definition.nodes.find((n) => n.uuid === nodeUuid);
969
+ if (!node) {
970
+ return;
971
+ }
972
+ // Get the node's position to place the selector near it
973
+ const nodeUI = this.definition._ui.nodes[nodeUuid];
974
+ if (!nodeUI) {
975
+ return;
976
+ }
977
+ // Show the node type selector in action mode, excluding branching actions
978
+ const selector = this.querySelector('temba-node-type-selector');
979
+ if (selector) {
980
+ // Show the selector near the node, using a mode that excludes branching actions
981
+ selector.show('action-no-branching', {
982
+ x: nodeUI.position.left,
983
+ y: nodeUI.position.top
984
+ });
985
+ // Store the node UUID so we know which node to add the action to
986
+ this.addActionToNodeUuid = nodeUuid;
987
+ }
988
+ }
778
989
  handleNodeEditRequested(event) {
779
990
  this.editingNode = event.detail.node;
780
991
  this.editingNodeUI = event.detail.nodeUI;
781
992
  }
782
993
  handleActionSaved(updatedAction) {
783
- var _a;
994
+ var _b, _c;
784
995
  if (this.editingNode && this.editingAction) {
785
- // Update the specific action in the node
786
- const updatedActions = this.editingNode.actions.map((action) => action.uuid === this.editingAction.uuid ? updatedAction : action);
996
+ let updatedActions;
997
+ // Check if this action already exists in the node
998
+ const existingActionIndex = this.editingNode.actions.findIndex((action) => action.uuid === this.editingAction.uuid);
999
+ if (existingActionIndex >= 0) {
1000
+ // Update existing action
1001
+ updatedActions = this.editingNode.actions.map((action) => action.uuid === this.editingAction.uuid ? updatedAction : action);
1002
+ }
1003
+ else {
1004
+ // Add new action
1005
+ updatedActions = [...this.editingNode.actions, updatedAction];
1006
+ }
787
1007
  const updatedNode = { ...this.editingNode, actions: updatedActions };
788
- // Update the node in the store
789
- (_a = getStore()) === null || _a === void 0 ? void 0 : _a.getState().updateNode(this.editingNode.uuid, updatedNode);
790
- // Repaint jsplumb connections in case node size changed
791
- if (this.plumber) {
792
- // Use requestAnimationFrame to ensure DOM has been updated first
793
- requestAnimationFrame(() => {
794
- this.plumber.repaintEverything();
795
- });
1008
+ // Check if we're creating a new node or updating an existing one
1009
+ if (this.isCreatingNewNode) {
1010
+ // This is a new node with a new action - add it to the store
1011
+ const store = getStore();
1012
+ const nodeUI = {
1013
+ position: this.pendingNodePosition || { left: 0, top: 0 },
1014
+ type: (_b = this.editingNodeUI) === null || _b === void 0 ? void 0 : _b.type,
1015
+ config: {}
1016
+ };
1017
+ // Add the node to the store
1018
+ store.getState().addNode(updatedNode, nodeUI);
1019
+ // Reset the creation flags
1020
+ this.isCreatingNewNode = false;
1021
+ this.pendingNodePosition = null;
1022
+ // Repaint jsplumb connections
1023
+ if (this.plumber) {
1024
+ requestAnimationFrame(() => {
1025
+ this.plumber.repaintEverything();
1026
+ });
1027
+ }
1028
+ }
1029
+ else {
1030
+ // Update existing node in the store
1031
+ (_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().updateNode(this.editingNode.uuid, updatedNode);
1032
+ // Repaint jsplumb connections in case node size changed
1033
+ if (this.plumber) {
1034
+ // Use requestAnimationFrame to ensure DOM has been updated first
1035
+ requestAnimationFrame(() => {
1036
+ this.plumber.repaintEverything();
1037
+ });
1038
+ }
796
1039
  }
797
1040
  }
798
1041
  this.closeNodeEditor();
@@ -803,25 +1046,51 @@ export class Editor extends RapidElement {
803
1046
  this.editingAction = null;
804
1047
  }
805
1048
  handleActionEditCanceled() {
1049
+ // If we were creating a new node, just discard it
1050
+ if (this.isCreatingNewNode) {
1051
+ this.isCreatingNewNode = false;
1052
+ this.pendingNodePosition = null;
1053
+ }
806
1054
  this.closeNodeEditor();
807
1055
  }
808
- handleNodeSaved(updatedNode) {
809
- var _a;
1056
+ handleNodeSaved(updatedNode, uiConfig) {
1057
+ var _b, _c, _d;
810
1058
  if (this.editingNode) {
811
- // Clean up jsPlumb connections for removed exits before updating the node
812
- if (this.plumber) {
813
- const oldExits = this.editingNode.exits || [];
814
- const newExits = updatedNode.exits || [];
815
- // Find exits that were removed
816
- const removedExits = oldExits.filter((oldExit) => !newExits.find((newExit) => newExit.uuid === oldExit.uuid));
817
- // Remove jsPlumb connections for removed exits
818
- removedExits.forEach((exit) => {
819
- this.plumber.removeExitConnection(exit.uuid);
820
- });
1059
+ if (this.isCreatingNewNode) {
1060
+ // This is a new node - add it to the store for the first time
1061
+ const store = getStore();
1062
+ const nodeUI = {
1063
+ position: this.pendingNodePosition || { left: 0, top: 0 },
1064
+ type: (_b = this.editingNodeUI) === null || _b === void 0 ? void 0 : _b.type,
1065
+ config: uiConfig || {}
1066
+ };
1067
+ // Add the node to the store
1068
+ store.getState().addNode(updatedNode, nodeUI);
1069
+ // Reset the creation flags
1070
+ this.isCreatingNewNode = false;
1071
+ this.pendingNodePosition = null;
1072
+ }
1073
+ else {
1074
+ // This is an existing node - update it
1075
+ // Clean up jsPlumb connections for removed exits before updating the node
1076
+ if (this.plumber) {
1077
+ const oldExits = this.editingNode.exits || [];
1078
+ const newExits = updatedNode.exits || [];
1079
+ // Find exits that were removed
1080
+ const removedExits = oldExits.filter((oldExit) => !newExits.find((newExit) => newExit.uuid === oldExit.uuid));
1081
+ // Remove jsPlumb connections for removed exits
1082
+ removedExits.forEach((exit) => {
1083
+ this.plumber.removeExitConnection(exit.uuid);
1084
+ });
1085
+ }
1086
+ this.plumber.revalidate([updatedNode.uuid]);
1087
+ // Update the node in the store
1088
+ (_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().updateNode(this.editingNode.uuid, updatedNode);
1089
+ // Update the UI config if provided
1090
+ if (uiConfig) {
1091
+ (_d = getStore()) === null || _d === void 0 ? void 0 : _d.getState().updateNodeUIConfig(updatedNode.uuid, uiConfig);
1092
+ }
821
1093
  }
822
- this.plumber.revalidate([updatedNode.uuid]);
823
- // Update the node in the store
824
- (_a = getStore()) === null || _a === void 0 ? void 0 : _a.getState().updateNode(this.editingNode.uuid, updatedNode);
825
1094
  // Repaint jsplumb connections in case node size changed
826
1095
  if (this.plumber) {
827
1096
  // Use requestAnimationFrame to ensure DOM has been updated first
@@ -833,16 +1102,224 @@ export class Editor extends RapidElement {
833
1102
  this.closeNodeEditor();
834
1103
  }
835
1104
  handleNodeEditCanceled() {
1105
+ // If we were creating a new node, just discard it
1106
+ if (this.isCreatingNewNode) {
1107
+ this.isCreatingNewNode = false;
1108
+ this.pendingNodePosition = null;
1109
+ }
836
1110
  this.closeNodeEditor();
837
1111
  }
1112
+ getNodeAtPosition(mouseX, mouseY) {
1113
+ // Get all node elements
1114
+ const nodeElements = this.querySelectorAll('temba-flow-node');
1115
+ for (const nodeElement of Array.from(nodeElements)) {
1116
+ const rect = nodeElement.getBoundingClientRect();
1117
+ if (mouseX >= rect.left &&
1118
+ mouseX <= rect.right &&
1119
+ mouseY >= rect.top &&
1120
+ mouseY <= rect.bottom) {
1121
+ return nodeElement.getAttribute('data-node-uuid');
1122
+ }
1123
+ }
1124
+ return null;
1125
+ }
1126
+ calculateCanvasDropPosition(mouseX, mouseY, applyGridSnapping = true) {
1127
+ // calculate the position on the canvas
1128
+ const canvas = this.querySelector('#canvas');
1129
+ if (!canvas)
1130
+ return { left: 0, top: 0 };
1131
+ const canvasRect = canvas.getBoundingClientRect();
1132
+ // calculate position relative to canvas
1133
+ // canvasRect gives us the canvas position in the viewport, which already accounts for scroll
1134
+ // so we just need mouseX/Y - canvasRect.left/top to get position within canvas
1135
+ const left = mouseX - canvasRect.left - DROP_PREVIEW_OFFSET_X;
1136
+ const top = mouseY - canvasRect.top - DROP_PREVIEW_OFFSET_Y;
1137
+ // Apply grid snapping only if requested (for final drop position)
1138
+ if (applyGridSnapping) {
1139
+ return {
1140
+ left: snapToGrid(left),
1141
+ top: snapToGrid(top)
1142
+ };
1143
+ }
1144
+ return { left, top };
1145
+ }
1146
+ handleActionDragExternal(event) {
1147
+ const { action, nodeUuid, actionIndex, mouseX, mouseY, actionHeight = 60 } = event.detail;
1148
+ // Check if mouse is over another execute_actions node
1149
+ const targetNode = this.getNodeAtPosition(mouseX, mouseY);
1150
+ if (targetNode && targetNode !== nodeUuid) {
1151
+ const targetNodeUI = this.definition._ui.nodes[targetNode];
1152
+ const targetNodeDef = this.definition.nodes.find((n) => n.uuid === targetNode);
1153
+ // Only allow dropping on execute_actions nodes, and not the source node
1154
+ if ((targetNodeUI === null || targetNodeUI === void 0 ? void 0 : targetNodeUI.type) === 'execute_actions' && targetNodeDef) {
1155
+ // If we moved to a different target node, clear the previous one's placeholder
1156
+ if (this.previousActionDragTargetNodeUuid &&
1157
+ this.previousActionDragTargetNodeUuid !== targetNode) {
1158
+ const previousElement = this.querySelector(`temba-flow-node[data-node-uuid="${this.previousActionDragTargetNodeUuid}"]`);
1159
+ if (previousElement) {
1160
+ previousElement.dispatchEvent(new CustomEvent('action-drag-leave', {
1161
+ detail: {},
1162
+ bubbles: false
1163
+ }));
1164
+ }
1165
+ }
1166
+ // Update target node for drop handling
1167
+ this.actionDragTargetNodeUuid = targetNode;
1168
+ this.previousActionDragTargetNodeUuid = targetNode;
1169
+ // Hide canvas preview when over a valid target
1170
+ this.canvasDropPreview = null;
1171
+ // Tell source node to show ghost (we're over a valid target)
1172
+ const sourceElement = this.querySelector(`temba-flow-node[data-node-uuid="${nodeUuid}"]`);
1173
+ if (sourceElement) {
1174
+ sourceElement.dispatchEvent(new CustomEvent('action-show-ghost', {
1175
+ detail: {},
1176
+ bubbles: false
1177
+ }));
1178
+ }
1179
+ // Notify the target node about the drag
1180
+ const targetElement = this.querySelector(`temba-flow-node[data-node-uuid="${targetNode}"]`);
1181
+ if (targetElement) {
1182
+ targetElement.dispatchEvent(new CustomEvent('action-drag-over', {
1183
+ detail: {
1184
+ action,
1185
+ sourceNodeUuid: nodeUuid,
1186
+ actionIndex,
1187
+ mouseX,
1188
+ mouseY,
1189
+ actionHeight
1190
+ },
1191
+ bubbles: false
1192
+ }));
1193
+ }
1194
+ this.requestUpdate();
1195
+ return;
1196
+ }
1197
+ }
1198
+ // Not over a valid target node, clear any previous target's placeholder
1199
+ if (this.previousActionDragTargetNodeUuid) {
1200
+ const previousElement = this.querySelector(`temba-flow-node[data-node-uuid="${this.previousActionDragTargetNodeUuid}"]`);
1201
+ if (previousElement) {
1202
+ previousElement.dispatchEvent(new CustomEvent('action-drag-leave', {
1203
+ detail: {},
1204
+ bubbles: false
1205
+ }));
1206
+ }
1207
+ this.previousActionDragTargetNodeUuid = null;
1208
+ }
1209
+ this.actionDragTargetNodeUuid = null;
1210
+ // Tell source node to hide ghost (we're not over a valid target)
1211
+ const sourceElement = this.querySelector(`temba-flow-node[data-node-uuid="${nodeUuid}"]`);
1212
+ if (sourceElement) {
1213
+ sourceElement.dispatchEvent(new CustomEvent('action-hide-ghost', {
1214
+ detail: {},
1215
+ bubbles: false
1216
+ }));
1217
+ }
1218
+ // Don't snap to grid for preview - let it follow cursor smoothly
1219
+ const position = this.calculateCanvasDropPosition(mouseX, mouseY, false);
1220
+ this.canvasDropPreview = {
1221
+ action,
1222
+ nodeUuid,
1223
+ actionIndex,
1224
+ position,
1225
+ actionHeight
1226
+ };
1227
+ // Force re-render to update preview position
1228
+ this.requestUpdate();
1229
+ }
1230
+ handleActionDragInternal(_event) {
1231
+ // Clear any previous target's placeholder when returning to internal drag
1232
+ if (this.previousActionDragTargetNodeUuid) {
1233
+ const previousElement = this.querySelector(`temba-flow-node[data-node-uuid="${this.previousActionDragTargetNodeUuid}"]`);
1234
+ if (previousElement) {
1235
+ previousElement.dispatchEvent(new CustomEvent('action-drag-leave', {
1236
+ detail: {},
1237
+ bubbles: false
1238
+ }));
1239
+ }
1240
+ this.previousActionDragTargetNodeUuid = null;
1241
+ }
1242
+ this.canvasDropPreview = null;
1243
+ this.actionDragTargetNodeUuid = null;
1244
+ }
1245
+ handleActionDropExternal(event) {
1246
+ var _b, _c, _d;
1247
+ const { action, nodeUuid, actionIndex, mouseX, mouseY } = event.detail;
1248
+ // Check if we're dropping on an existing execute_actions node
1249
+ const targetNodeUuid = this.actionDragTargetNodeUuid;
1250
+ if (targetNodeUuid && targetNodeUuid !== nodeUuid) {
1251
+ // Dropping on another node - notify the target node to handle the drop
1252
+ const targetElement = this.querySelector(`temba-flow-node[data-node-uuid="${targetNodeUuid}"]`);
1253
+ if (targetElement) {
1254
+ targetElement.dispatchEvent(new CustomEvent('action-drop', {
1255
+ detail: {
1256
+ action,
1257
+ sourceNodeUuid: nodeUuid,
1258
+ actionIndex,
1259
+ mouseX,
1260
+ mouseY
1261
+ },
1262
+ bubbles: false
1263
+ }));
1264
+ }
1265
+ // Clear state
1266
+ this.canvasDropPreview = null;
1267
+ this.actionDragTargetNodeUuid = null;
1268
+ return;
1269
+ }
1270
+ // Not dropping on another node, create a new one on canvas
1271
+ // Snap to grid for the final drop position
1272
+ const position = this.calculateCanvasDropPosition(mouseX, mouseY, true);
1273
+ // remove the action from the original node
1274
+ const originalNode = this.definition.nodes.find((n) => n.uuid === nodeUuid);
1275
+ if (!originalNode)
1276
+ return;
1277
+ const updatedActions = originalNode.actions.filter((_a, idx) => idx !== actionIndex);
1278
+ // if no actions remain, delete the node
1279
+ if (updatedActions.length === 0) {
1280
+ (_b = getStore()) === null || _b === void 0 ? void 0 : _b.getState().removeNodes([nodeUuid]);
1281
+ }
1282
+ else {
1283
+ // update the node
1284
+ const updatedNode = { ...originalNode, actions: updatedActions };
1285
+ (_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().updateNode(nodeUuid, updatedNode);
1286
+ }
1287
+ // create a new execute_actions node with the dropped action
1288
+ const newNode = {
1289
+ uuid: generateUUID(),
1290
+ actions: [action],
1291
+ exits: [
1292
+ {
1293
+ uuid: generateUUID(),
1294
+ destination_uuid: null
1295
+ }
1296
+ ]
1297
+ };
1298
+ const newNodeUI = {
1299
+ position,
1300
+ type: 'execute_actions',
1301
+ config: {}
1302
+ };
1303
+ // add the new node
1304
+ (_d = getStore()) === null || _d === void 0 ? void 0 : _d.getState().addNode(newNode, newNodeUI);
1305
+ // clear the preview
1306
+ this.canvasDropPreview = null;
1307
+ this.actionDragTargetNodeUuid = null;
1308
+ // repaint connections
1309
+ if (this.plumber) {
1310
+ requestAnimationFrame(() => {
1311
+ this.plumber.repaintEverything();
1312
+ });
1313
+ }
1314
+ }
838
1315
  render() {
839
- var _a, _b;
1316
+ var _b, _c;
840
1317
  // we have to embed our own style since we are in light DOM
841
1318
  const style = html `<style>
842
1319
  ${unsafeCSS(Editor.styles.cssText)}
843
1320
  ${unsafeCSS(CanvasNode.styles.cssText)}
844
1321
  </style>`;
845
- const stickies = ((_b = (_a = this.definition) === null || _a === void 0 ? void 0 : _a._ui) === null || _b === void 0 ? void 0 : _b.stickies) || {};
1322
+ const stickies = ((_c = (_b = this.definition) === null || _b === void 0 ? void 0 : _b._ui) === null || _c === void 0 ? void 0 : _c.stickies) || {};
846
1323
  return html `${style}
847
1324
  <div id="editor">
848
1325
  <div
@@ -853,13 +1330,13 @@ export class Editor extends RapidElement {
853
1330
  <div id="canvas">
854
1331
  ${this.definition
855
1332
  ? repeat(this.definition.nodes, (node) => node.uuid, (node) => {
856
- var _a, _b, _c;
857
- const position = ((_b = (_a = this.definition._ui) === null || _a === void 0 ? void 0 : _a.nodes[node.uuid]) === null || _b === void 0 ? void 0 : _b.position) || {
1333
+ var _b, _c, _d;
1334
+ const position = ((_c = (_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.nodes[node.uuid]) === null || _c === void 0 ? void 0 : _c.position) || {
858
1335
  left: 0,
859
1336
  top: 0
860
1337
  };
861
1338
  const dragging = this.isDragging &&
862
- ((_c = this.currentDragItem) === null || _c === void 0 ? void 0 : _c.uuid) === node.uuid;
1339
+ ((_d = this.currentDragItem) === null || _d === void 0 ? void 0 : _d.uuid) === node.uuid;
863
1340
  const selected = this.selectedItems.has(node.uuid);
864
1341
  return html `<temba-flow-node
865
1342
  class="draggable ${dragging ? 'dragging' : ''} ${selected
@@ -867,6 +1344,7 @@ export class Editor extends RapidElement {
867
1344
  : ''}"
868
1345
  @mousedown=${this.handleMouseDown.bind(this)}
869
1346
  uuid=${node.uuid}
1347
+ data-node-uuid=${node.uuid}
870
1348
  style="left:${position.left}px; top:${position.top}px"
871
1349
  .plumber=${this.plumber}
872
1350
  .node=${node}
@@ -878,9 +1356,9 @@ export class Editor extends RapidElement {
878
1356
  })
879
1357
  : html `<temba-loading></temba-loading>`}
880
1358
  ${repeat(Object.entries(stickies), ([uuid]) => uuid, ([uuid, sticky]) => {
881
- var _a;
1359
+ var _b;
882
1360
  const position = sticky.position || { left: 0, top: 0 };
883
- const dragging = this.isDragging && ((_a = this.currentDragItem) === null || _a === void 0 ? void 0 : _a.uuid) === uuid;
1361
+ const dragging = this.isDragging && ((_b = this.currentDragItem) === null || _b === void 0 ? void 0 : _b.uuid) === uuid;
884
1362
  const selected = this.selectedItems.has(uuid);
885
1363
  return html `<temba-sticky-note
886
1364
  class="draggable ${dragging ? 'dragging' : ''} ${selected
@@ -895,7 +1373,7 @@ export class Editor extends RapidElement {
895
1373
  .selected=${selected}
896
1374
  ></temba-sticky-note>`;
897
1375
  })}
898
- ${this.renderSelectionBox()}
1376
+ ${this.renderSelectionBox()} ${this.renderCanvasDropPreview()}
899
1377
  </div>
900
1378
  </div>
901
1379
  </div>
@@ -905,11 +1383,14 @@ export class Editor extends RapidElement {
905
1383
  .node=${this.editingNode}
906
1384
  .nodeUI=${this.editingNodeUI}
907
1385
  .action=${this.editingAction}
908
- @temba-node-saved=${(e) => this.handleNodeSaved(e.detail.node)}
1386
+ @temba-node-saved=${(e) => this.handleNodeSaved(e.detail.node, e.detail.uiConfig)}
909
1387
  @temba-action-saved=${(e) => this.handleActionSaved(e.detail.action)}
910
1388
  @temba-node-edit-cancelled=${this.handleNodeEditCanceled}
911
1389
  ></temba-node-editor>`
912
- : ''} `;
1390
+ : ''}
1391
+
1392
+ <temba-canvas-menu></temba-canvas-menu>
1393
+ <temba-node-type-selector></temba-node-type-selector> `;
913
1394
  }
914
1395
  }
915
1396
  __decorate([
@@ -963,4 +1444,19 @@ __decorate([
963
1444
  __decorate([
964
1445
  state()
965
1446
  ], Editor.prototype, "editingAction", void 0);
1447
+ __decorate([
1448
+ state()
1449
+ ], Editor.prototype, "isCreatingNewNode", void 0);
1450
+ __decorate([
1451
+ state()
1452
+ ], Editor.prototype, "pendingNodePosition", void 0);
1453
+ __decorate([
1454
+ state()
1455
+ ], Editor.prototype, "canvasDropPreview", void 0);
1456
+ __decorate([
1457
+ state()
1458
+ ], Editor.prototype, "addActionToNodeUuid", void 0);
1459
+ __decorate([
1460
+ state()
1461
+ ], Editor.prototype, "actionDragTargetNodeUuid", void 0);
966
1462
  //# sourceMappingURL=Editor.js.map