@nyaruka/temba-components 0.131.1 → 0.131.3

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 (485) hide show
  1. package/.github/workflows/publish.yml +4 -1
  2. package/CHANGELOG.md +75 -1
  3. package/demo/components/floating-tabs/example.html +400 -0
  4. package/demo/components/flow/index.html +1 -1
  5. package/demo/data/flows/food-order.json +2 -2
  6. package/demo/data/flows/sample-flow.json +113 -125
  7. package/demo/data/flows/voicemail.json +613 -0
  8. package/demo/index.html +6 -0
  9. package/dist/locales/es.js +5 -5
  10. package/dist/locales/es.js.map +1 -1
  11. package/dist/locales/fr.js +5 -5
  12. package/dist/locales/fr.js.map +1 -1
  13. package/dist/locales/locale-codes.js +11 -2
  14. package/dist/locales/locale-codes.js.map +1 -1
  15. package/dist/locales/pt.js +5 -5
  16. package/dist/locales/pt.js.map +1 -1
  17. package/dist/static/svg/index.svg +1 -1
  18. package/dist/temba-components.js +1773 -662
  19. package/dist/temba-components.js.map +1 -1
  20. package/out-tsc/src/Icons.js +4 -1
  21. package/out-tsc/src/Icons.js.map +1 -1
  22. package/out-tsc/src/display/FloatingTab.js +167 -0
  23. package/out-tsc/src/display/FloatingTab.js.map +1 -0
  24. package/out-tsc/src/display/ProgressBar.js +22 -2
  25. package/out-tsc/src/display/ProgressBar.js.map +1 -1
  26. package/out-tsc/src/events.js.map +1 -1
  27. package/out-tsc/src/flow/CanvasMenu.js +200 -0
  28. package/out-tsc/src/flow/CanvasMenu.js.map +1 -0
  29. package/out-tsc/src/flow/CanvasNode.js +489 -47
  30. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  31. package/out-tsc/src/flow/Editor.js +1417 -67
  32. package/out-tsc/src/flow/Editor.js.map +1 -1
  33. package/out-tsc/src/flow/NodeEditor.js +479 -112
  34. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  35. package/out-tsc/src/flow/NodeTypeSelector.js +540 -0
  36. package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -0
  37. package/out-tsc/src/flow/StickyNote.js +12 -3
  38. package/out-tsc/src/flow/StickyNote.js.map +1 -1
  39. package/out-tsc/src/flow/actions/add_contact_groups.js +4 -3
  40. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
  41. package/out-tsc/src/flow/actions/add_contact_urn.js +63 -4
  42. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
  43. package/out-tsc/src/flow/actions/add_input_labels.js +4 -3
  44. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  45. package/out-tsc/src/flow/actions/play_audio.js +3 -2
  46. package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
  47. package/out-tsc/src/flow/actions/remove_contact_groups.js +7 -5
  48. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
  49. package/out-tsc/src/flow/actions/request_optin.js +3 -2
  50. package/out-tsc/src/flow/actions/request_optin.js.map +1 -1
  51. package/out-tsc/src/flow/actions/say_msg.js +3 -2
  52. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  53. package/out-tsc/src/flow/actions/send_broadcast.js +77 -23
  54. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
  55. package/out-tsc/src/flow/actions/send_email.js +5 -5
  56. package/out-tsc/src/flow/actions/send_email.js.map +1 -1
  57. package/out-tsc/src/flow/actions/send_msg.js +101 -21
  58. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  59. package/out-tsc/src/flow/actions/set_contact_channel.js +6 -9
  60. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  61. package/out-tsc/src/flow/actions/set_contact_field.js +20 -20
  62. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  63. package/out-tsc/src/flow/actions/set_contact_language.js +3 -2
  64. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  65. package/out-tsc/src/flow/actions/set_contact_name.js +3 -12
  66. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
  67. package/out-tsc/src/flow/actions/set_contact_status.js +3 -2
  68. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  69. package/out-tsc/src/flow/actions/set_run_result.js +4 -3
  70. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  71. package/out-tsc/src/flow/actions/start_session.js +181 -6
  72. package/out-tsc/src/flow/actions/start_session.js.map +1 -1
  73. package/out-tsc/src/flow/config.js +11 -23
  74. package/out-tsc/src/flow/config.js.map +1 -1
  75. package/out-tsc/src/flow/currencies.js +45 -0
  76. package/out-tsc/src/flow/currencies.js.map +1 -0
  77. package/out-tsc/src/flow/nodes/shared-rules.js +257 -0
  78. package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -0
  79. package/out-tsc/src/flow/nodes/shared.js +71 -0
  80. package/out-tsc/src/flow/nodes/shared.js.map +1 -0
  81. package/out-tsc/src/flow/nodes/split_by_airtime.js +211 -5
  82. package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -1
  83. package/out-tsc/src/flow/nodes/split_by_contact_field.js +152 -3
  84. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
  85. package/out-tsc/src/flow/nodes/split_by_expression.js +73 -2
  86. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
  87. package/out-tsc/src/flow/nodes/split_by_groups.js +18 -10
  88. package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
  89. package/out-tsc/src/flow/nodes/split_by_intent.js +8 -0
  90. package/out-tsc/src/flow/nodes/split_by_intent.js.map +1 -0
  91. package/out-tsc/src/flow/nodes/split_by_llm.js +11 -3
  92. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
  93. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +10 -3
  94. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  95. package/out-tsc/src/flow/nodes/split_by_random.js +10 -4
  96. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
  97. package/out-tsc/src/flow/nodes/split_by_resthook.js +113 -0
  98. package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -0
  99. package/out-tsc/src/flow/nodes/split_by_run_result.js +211 -3
  100. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
  101. package/out-tsc/src/flow/nodes/split_by_scheme.js +158 -2
  102. package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -1
  103. package/out-tsc/src/flow/nodes/split_by_subflow.js +13 -5
  104. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
  105. package/out-tsc/src/flow/nodes/split_by_ticket.js +10 -3
  106. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
  107. package/out-tsc/src/flow/nodes/split_by_webhook.js +10 -3
  108. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
  109. package/out-tsc/src/flow/nodes/wait_for_digits.js +3 -2
  110. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  111. package/out-tsc/src/flow/nodes/wait_for_menu.js +3 -2
  112. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
  113. package/out-tsc/src/flow/nodes/wait_for_response.js +38 -568
  114. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  115. package/out-tsc/src/flow/types.js +86 -12
  116. package/out-tsc/src/flow/types.js.map +1 -1
  117. package/out-tsc/src/flow/utils.js +101 -14
  118. package/out-tsc/src/flow/utils.js.map +1 -1
  119. package/out-tsc/src/form/FieldRenderer.js +2 -4
  120. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  121. package/out-tsc/src/interfaces.js +3 -0
  122. package/out-tsc/src/interfaces.js.map +1 -1
  123. package/out-tsc/src/layout/FloatingWindow.js +346 -0
  124. package/out-tsc/src/layout/FloatingWindow.js.map +1 -0
  125. package/out-tsc/src/list/SortableList.js +98 -33
  126. package/out-tsc/src/list/SortableList.js.map +1 -1
  127. package/out-tsc/src/live/ContactChat.js +6 -25
  128. package/out-tsc/src/live/ContactChat.js.map +1 -1
  129. package/out-tsc/src/locales/es.js +5 -5
  130. package/out-tsc/src/locales/es.js.map +1 -1
  131. package/out-tsc/src/locales/fr.js +5 -5
  132. package/out-tsc/src/locales/fr.js.map +1 -1
  133. package/out-tsc/src/locales/locale-codes.js +11 -2
  134. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  135. package/out-tsc/src/locales/pt.js +5 -5
  136. package/out-tsc/src/locales/pt.js.map +1 -1
  137. package/out-tsc/src/store/AppState.js +120 -0
  138. package/out-tsc/src/store/AppState.js.map +1 -1
  139. package/out-tsc/src/utils.js +254 -13
  140. package/out-tsc/src/utils.js.map +1 -1
  141. package/out-tsc/temba-modules.js +8 -0
  142. package/out-tsc/temba-modules.js.map +1 -1
  143. package/out-tsc/test/ActionHelper.js +3 -3
  144. package/out-tsc/test/ActionHelper.js.map +1 -1
  145. package/out-tsc/test/NodeHelper.js +6 -3
  146. package/out-tsc/test/NodeHelper.js.map +1 -1
  147. package/out-tsc/test/actions/add_contact_urn.test.js +202 -0
  148. package/out-tsc/test/actions/add_contact_urn.test.js.map +1 -0
  149. package/out-tsc/test/actions/send_broadcast.test.js +148 -0
  150. package/out-tsc/test/actions/send_broadcast.test.js.map +1 -0
  151. package/out-tsc/test/actions/send_email.test.js +17 -23
  152. package/out-tsc/test/actions/send_email.test.js.map +1 -1
  153. package/out-tsc/test/actions/send_msg.test.js +33 -15
  154. package/out-tsc/test/actions/send_msg.test.js.map +1 -1
  155. package/out-tsc/test/actions/start_session.test.js +116 -0
  156. package/out-tsc/test/actions/start_session.test.js.map +1 -0
  157. package/out-tsc/test/nodes/split_by_airtime.test.js +604 -0
  158. package/out-tsc/test/nodes/split_by_airtime.test.js.map +1 -0
  159. package/out-tsc/test/nodes/split_by_contact_field.test.js +387 -0
  160. package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -0
  161. package/out-tsc/test/nodes/split_by_expression.test.js +614 -0
  162. package/out-tsc/test/nodes/split_by_expression.test.js.map +1 -0
  163. package/out-tsc/test/nodes/split_by_random.test.js +3 -3
  164. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
  165. package/out-tsc/test/nodes/split_by_resthook.test.js +337 -0
  166. package/out-tsc/test/nodes/split_by_resthook.test.js.map +1 -0
  167. package/out-tsc/test/nodes/split_by_run_result.test.js +920 -0
  168. package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -0
  169. package/out-tsc/test/nodes/split_by_scheme.test.js +399 -0
  170. package/out-tsc/test/nodes/split_by_scheme.test.js.map +1 -0
  171. package/out-tsc/test/nodes/split_by_subflow.test.js +333 -0
  172. package/out-tsc/test/nodes/split_by_subflow.test.js.map +1 -0
  173. package/out-tsc/test/nodes/wait_for_digits.test.js +2 -2
  174. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  175. package/out-tsc/test/nodes/wait_for_response.test.js +2 -1
  176. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
  177. package/out-tsc/test/temba-action-drag-between-nodes.test.js +252 -0
  178. package/out-tsc/test/temba-action-drag-between-nodes.test.js.map +1 -0
  179. package/out-tsc/test/temba-canvas-menu.test.js +122 -0
  180. package/out-tsc/test/temba-canvas-menu.test.js.map +1 -0
  181. package/out-tsc/test/temba-floating-tab.test.js +91 -0
  182. package/out-tsc/test/temba-floating-tab.test.js.map +1 -0
  183. package/out-tsc/test/temba-floating-window.test.js +301 -0
  184. package/out-tsc/test/temba-floating-window.test.js.map +1 -0
  185. package/out-tsc/test/temba-flow-editor-node.test.js +202 -2
  186. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  187. package/out-tsc/test/temba-flow-editor.test.js +7 -8
  188. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  189. package/out-tsc/test/temba-localization.test.js +471 -0
  190. package/out-tsc/test/temba-localization.test.js.map +1 -0
  191. package/out-tsc/test/temba-node-editor.test.js +3 -1
  192. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  193. package/out-tsc/test/temba-node-type-selector.test.js +265 -0
  194. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -0
  195. package/out-tsc/test/temba-omnibox.test.js +2 -1
  196. package/out-tsc/test/temba-omnibox.test.js.map +1 -1
  197. package/out-tsc/test/temba-sortable-list.test.js +51 -0
  198. package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
  199. package/out-tsc/test/temba-utils-index.test.js +1 -27
  200. package/out-tsc/test/temba-utils-index.test.js.map +1 -1
  201. package/out-tsc/test/utils.test.js +20 -0
  202. package/out-tsc/test/utils.test.js.map +1 -1
  203. package/package.json +2 -1
  204. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  205. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  206. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  207. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  208. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  209. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  210. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  211. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  212. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  213. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  214. package/screenshots/truth/actions/add_contact_urn/editor/expression-facebook.png +0 -0
  215. package/screenshots/truth/actions/add_contact_urn/editor/expression-phone.png +0 -0
  216. package/screenshots/truth/actions/add_contact_urn/editor/facebook-id.png +0 -0
  217. package/screenshots/truth/actions/add_contact_urn/editor/instagram-handle.png +0 -0
  218. package/screenshots/truth/actions/add_contact_urn/editor/line-id.png +0 -0
  219. package/screenshots/truth/actions/add_contact_urn/editor/phone-number.png +0 -0
  220. package/screenshots/truth/actions/add_contact_urn/editor/telegram-id.png +0 -0
  221. package/screenshots/truth/actions/add_contact_urn/editor/viber-id.png +0 -0
  222. package/screenshots/truth/actions/add_contact_urn/editor/wechat-id.png +0 -0
  223. package/screenshots/truth/actions/add_contact_urn/editor/whatsapp.png +0 -0
  224. package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
  225. package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
  226. package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
  227. package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
  228. package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
  229. package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
  230. package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
  231. package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
  232. package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
  233. package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
  234. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  235. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  236. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  237. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  238. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  239. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  240. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  241. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  242. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  243. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  244. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  245. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  246. package/screenshots/truth/actions/send_broadcast/editor/contacts-only.png +0 -0
  247. package/screenshots/truth/actions/send_broadcast/editor/groups-and-contacts.png +0 -0
  248. package/screenshots/truth/actions/send_broadcast/editor/groups-only.png +0 -0
  249. package/screenshots/truth/actions/send_broadcast/editor/many-groups.png +0 -0
  250. package/screenshots/truth/actions/send_broadcast/editor/multiline-text.png +0 -0
  251. package/screenshots/truth/actions/send_broadcast/editor/with-attachments.png +0 -0
  252. package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
  253. package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
  254. package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
  255. package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
  256. package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
  257. package/screenshots/truth/actions/send_broadcast/render/with-attachments.png +0 -0
  258. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  259. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  260. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  261. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  262. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  263. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  264. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  265. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  266. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  267. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  268. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  269. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  270. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  271. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  272. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  273. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  274. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  275. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  276. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  277. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  278. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  279. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  280. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  281. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  282. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  283. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  284. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  285. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  286. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  287. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  288. package/screenshots/truth/actions/start_session/editor/contact-query.png +0 -0
  289. package/screenshots/truth/actions/start_session/editor/contacts-only.png +0 -0
  290. package/screenshots/truth/actions/start_session/editor/create-contact.png +0 -0
  291. package/screenshots/truth/actions/start_session/editor/groups-and-contacts.png +0 -0
  292. package/screenshots/truth/actions/start_session/editor/groups-only.png +0 -0
  293. package/screenshots/truth/actions/start_session/editor/many-recipients.png +0 -0
  294. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  295. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  296. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  297. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  298. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  299. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  300. package/screenshots/truth/canvas-menu/open.png +0 -0
  301. package/screenshots/truth/editor/router.png +0 -0
  302. package/screenshots/truth/editor/wait.png +0 -0
  303. package/screenshots/truth/floating-tab/default.png +0 -0
  304. package/screenshots/truth/floating-tab/gray.png +0 -0
  305. package/screenshots/truth/floating-tab/green.png +0 -0
  306. package/screenshots/truth/floating-tab/hidden.png +0 -0
  307. package/screenshots/truth/floating-tab/hover.png +0 -0
  308. package/screenshots/truth/floating-tab/purple.png +0 -0
  309. package/screenshots/truth/floating-window/chromeless.png +0 -0
  310. package/screenshots/truth/floating-window/custom-size.png +0 -0
  311. package/screenshots/truth/floating-window/default.png +0 -0
  312. package/screenshots/truth/floating-window/with-header.png +0 -0
  313. package/screenshots/truth/list/fields-dragging.png +0 -0
  314. package/screenshots/truth/list/sortable-dragging.png +0 -0
  315. package/screenshots/truth/node-type-selector/action-mode.png +0 -0
  316. package/screenshots/truth/node-type-selector/split-mode.png +0 -0
  317. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  318. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  319. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  320. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  321. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  322. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  323. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  324. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  325. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  326. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  327. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  328. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  329. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  330. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  331. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  332. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  333. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  334. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  335. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  336. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  337. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  338. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  339. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  340. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  341. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  342. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  343. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  344. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  345. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  346. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  347. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  348. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  349. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  350. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  351. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  352. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  353. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  354. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  355. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  356. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  357. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  358. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  359. package/src/Icons.ts +4 -1
  360. package/src/display/FloatingTab.ts +174 -0
  361. package/src/display/ProgressBar.ts +22 -2
  362. package/src/events.ts +2 -8
  363. package/src/flow/CanvasMenu.ts +217 -0
  364. package/src/flow/CanvasNode.ts +596 -40
  365. package/src/flow/Editor.ts +1721 -45
  366. package/src/flow/NodeEditor.ts +621 -144
  367. package/src/flow/NodeTypeSelector.ts +636 -0
  368. package/src/flow/StickyNote.ts +12 -3
  369. package/src/flow/actions/add_contact_groups.ts +5 -4
  370. package/src/flow/actions/add_contact_urn.ts +78 -4
  371. package/src/flow/actions/add_input_labels.ts +5 -4
  372. package/src/flow/actions/play_audio.ts +3 -2
  373. package/src/flow/actions/remove_contact_groups.ts +16 -6
  374. package/src/flow/actions/request_optin.ts +3 -2
  375. package/src/flow/actions/say_msg.ts +3 -2
  376. package/src/flow/actions/send_broadcast.ts +86 -23
  377. package/src/flow/actions/send_email.ts +12 -6
  378. package/src/flow/actions/send_msg.ts +155 -34
  379. package/src/flow/actions/set_contact_channel.ts +6 -11
  380. package/src/flow/actions/set_contact_field.ts +21 -25
  381. package/src/flow/actions/set_contact_language.ts +11 -4
  382. package/src/flow/actions/set_contact_name.ts +4 -15
  383. package/src/flow/actions/set_contact_status.ts +4 -3
  384. package/src/flow/actions/set_run_result.ts +5 -4
  385. package/src/flow/actions/start_session.ts +210 -6
  386. package/src/flow/config.ts +11 -23
  387. package/src/flow/currencies.ts +51 -0
  388. package/src/flow/nodes/shared-rules.ts +301 -0
  389. package/src/flow/nodes/shared.ts +87 -0
  390. package/src/flow/nodes/split_by_airtime.ts +255 -5
  391. package/src/flow/nodes/split_by_contact_field.ts +195 -3
  392. package/src/flow/nodes/split_by_expression.ts +104 -2
  393. package/src/flow/nodes/split_by_groups.ts +26 -11
  394. package/src/flow/nodes/split_by_intent.ts +8 -0
  395. package/src/flow/nodes/split_by_llm.ts +22 -4
  396. package/src/flow/nodes/split_by_llm_categorize.ts +22 -5
  397. package/src/flow/nodes/split_by_random.ts +16 -6
  398. package/src/flow/nodes/split_by_resthook.ts +140 -0
  399. package/src/flow/nodes/split_by_run_result.ts +259 -3
  400. package/src/flow/nodes/split_by_scheme.ts +202 -2
  401. package/src/flow/nodes/split_by_subflow.ts +17 -5
  402. package/src/flow/nodes/split_by_ticket.ts +15 -4
  403. package/src/flow/nodes/split_by_webhook.ts +17 -6
  404. package/src/flow/nodes/wait_for_digits.ts +3 -2
  405. package/src/flow/nodes/wait_for_menu.ts +3 -2
  406. package/src/flow/nodes/wait_for_response.ts +59 -680
  407. package/src/flow/types.ts +156 -23
  408. package/src/flow/utils.ts +108 -14
  409. package/src/form/FieldRenderer.ts +2 -4
  410. package/src/interfaces.ts +3 -0
  411. package/src/layout/FloatingWindow.ts +386 -0
  412. package/src/list/SortableList.ts +109 -34
  413. package/src/live/ContactChat.ts +7 -25
  414. package/src/locales/es.ts +18 -13
  415. package/src/locales/fr.ts +18 -13
  416. package/src/locales/locale-codes.ts +11 -2
  417. package/src/locales/pt.ts +18 -13
  418. package/src/store/AppState.ts +173 -0
  419. package/src/store/flow-definition.d.ts +2 -5
  420. package/src/utils.ts +332 -12
  421. package/static/api/channels.json +46 -0
  422. package/static/api/llms.json +18 -0
  423. package/static/api/resthooks.json +31 -0
  424. package/static/svg/index.svg +1 -1
  425. package/static/svg/work/traced/lightning-02.svg +1 -0
  426. package/static/svg/work/used/lightning-02.svg +3 -0
  427. package/temba-modules.ts +8 -0
  428. package/test/ActionHelper.ts +3 -3
  429. package/test/NodeHelper.ts +6 -3
  430. package/test/actions/add_contact_urn.test.ts +287 -0
  431. package/test/actions/send_broadcast.test.ts +190 -0
  432. package/test/actions/send_email.test.ts +17 -23
  433. package/test/actions/send_msg.test.ts +39 -15
  434. package/test/actions/start_session.test.ts +151 -0
  435. package/test/nodes/split_by_airtime.test.ts +673 -0
  436. package/test/nodes/split_by_contact_field.test.ts +451 -0
  437. package/test/nodes/split_by_expression.test.ts +751 -0
  438. package/test/nodes/split_by_random.test.ts +3 -3
  439. package/test/nodes/split_by_resthook.test.ts +398 -0
  440. package/test/nodes/split_by_run_result.test.ts +1109 -0
  441. package/test/nodes/split_by_scheme.test.ts +486 -0
  442. package/test/nodes/split_by_subflow.test.ts +381 -0
  443. package/test/nodes/wait_for_digits.test.ts +2 -2
  444. package/test/nodes/wait_for_response.test.ts +2 -1
  445. package/test/temba-action-drag-between-nodes.test.ts +301 -0
  446. package/test/temba-canvas-menu.test.ts +156 -0
  447. package/test/temba-floating-tab.test.ts +110 -0
  448. package/test/temba-floating-window.test.ts +477 -0
  449. package/test/temba-flow-editor-node.test.ts +246 -2
  450. package/test/temba-flow-editor.test.ts +7 -8
  451. package/test/temba-localization.test.ts +611 -0
  452. package/test/temba-node-editor.test.ts +3 -1
  453. package/test/temba-node-type-selector.test.ts +355 -0
  454. package/test/temba-omnibox.test.ts +2 -1
  455. package/test/temba-sortable-list.test.ts +69 -0
  456. package/test/temba-utils-index.test.ts +0 -35
  457. package/test/utils.test.ts +22 -0
  458. package/test-assets/contacts/history.json +14 -21
  459. package/test-assets/select/llms.json +2 -2
  460. package/web-dev-server.config.mjs +49 -1
  461. package/web-test-runner.config.mjs +0 -1
  462. package/out-tsc/src/flow/actions/call_classifier.js +0 -11
  463. package/out-tsc/src/flow/actions/call_classifier.js.map +0 -1
  464. package/out-tsc/src/flow/actions/call_resthook.js +0 -11
  465. package/out-tsc/src/flow/actions/call_resthook.js.map +0 -1
  466. package/out-tsc/src/flow/actions/split_by_expression_example.js +0 -77
  467. package/out-tsc/src/flow/actions/split_by_expression_example.js.map +0 -1
  468. package/out-tsc/src/flow/actions/transfer_airtime.js +0 -11
  469. package/out-tsc/src/flow/actions/transfer_airtime.js.map +0 -1
  470. package/out-tsc/src/flow/nodes/wait_for_audio.js +0 -7
  471. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +0 -1
  472. package/out-tsc/src/flow/nodes/wait_for_image.js +0 -7
  473. package/out-tsc/src/flow/nodes/wait_for_image.js.map +0 -1
  474. package/out-tsc/src/flow/nodes/wait_for_location.js +0 -7
  475. package/out-tsc/src/flow/nodes/wait_for_location.js.map +0 -1
  476. package/out-tsc/src/flow/nodes/wait_for_video.js +0 -7
  477. package/out-tsc/src/flow/nodes/wait_for_video.js.map +0 -1
  478. package/src/flow/actions/call_classifier.ts +0 -12
  479. package/src/flow/actions/call_resthook.ts +0 -12
  480. package/src/flow/actions/split_by_expression_example.ts +0 -88
  481. package/src/flow/actions/transfer_airtime.ts +0 -12
  482. package/src/flow/nodes/wait_for_audio.ts +0 -7
  483. package/src/flow/nodes/wait_for_image.ts +0 -7
  484. package/src/flow/nodes/wait_for_location.ts +0 -7
  485. package/src/flow/nodes/wait_for_video.ts +0 -7
@@ -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';
@@ -8,6 +9,7 @@ import { getClasses } from '../utils';
8
9
  import { Plumber } from './Plumber';
9
10
  import { getStore } from '../store/Store';
10
11
  import { CustomEventType } from '../interfaces';
12
+ import { AppState, fromStore, zustand } from '../store/AppState';
11
13
 
12
14
  const DRAG_THRESHOLD = 5;
13
15
 
@@ -25,6 +27,22 @@ export class CanvasNode extends RapidElement {
25
27
  @property({ type: Object })
26
28
  private ui: NodeUI;
27
29
 
30
+ @fromStore(zustand, (state: AppState) => state.isTranslating)
31
+ private isTranslating!: boolean;
32
+
33
+ @fromStore(zustand, (state: AppState) => state.languageCode)
34
+ private languageCode!: string;
35
+
36
+ @fromStore(zustand, (state: AppState) => state.flowDefinition)
37
+ private flowDefinition!: any;
38
+
39
+ @fromStore(
40
+ zustand,
41
+ (state: AppState) =>
42
+ state.flowDefinition?._ui?.translation_filters?.categories || false
43
+ )
44
+ private includeCategoriesInTranslation!: boolean;
45
+
28
46
  // Track exits that are in "removing" state
29
47
  private exitRemovalTimeouts: Map<string, number> = new Map();
30
48
 
@@ -46,6 +64,18 @@ export class CanvasNode extends RapidElement {
46
64
  private nodeClickStartPos: { x: number; y: number } | null = null;
47
65
  private pendingNodeClick: { event: MouseEvent } | null = null;
48
66
 
67
+ // Track the height of the action being dragged (captured at drag start)
68
+ private draggedActionHeight: number = 0;
69
+
70
+ // Track external action drag (action being dragged from another node)
71
+ private externalDragInfo: {
72
+ action: Action;
73
+ sourceNodeUuid: string;
74
+ actionIndex: number;
75
+ dropIndex: number;
76
+ actionHeight: number;
77
+ } | null = null;
78
+
49
79
  static get styles() {
50
80
  return css`
51
81
 
@@ -147,6 +177,20 @@ export class CanvasNode extends RapidElement {
147
177
 
148
178
  .node.execute-actions temba-sortable-list .action:last-child .body {
149
179
  padding-bottom: 1.5em;
180
+ }
181
+
182
+ /* Localization indicators */
183
+ .action.localizable:not(.has-localization) .action-content {
184
+ background: #fff8dc !important; /* Light yellow background for localizable but not yet localized */
185
+ }
186
+
187
+ .non-localizable {
188
+ opacity: 0.25;
189
+ pointer-events: none;
190
+ }
191
+
192
+ .action.non-localizable .action-content {
193
+ cursor: not-allowed;
150
194
  }
151
195
 
152
196
  .action .drag-handle {
@@ -160,7 +204,7 @@ export class CanvasNode extends RapidElement {
160
204
  pointer-events: auto; /* Ensure drag handle can receive events */
161
205
  }
162
206
  .title-spacer {
163
- width: 2em;
207
+ width: 1.8em;
164
208
 
165
209
  }
166
210
 
@@ -215,6 +259,10 @@ export class CanvasNode extends RapidElement {
215
259
  margin: 0.2em;
216
260
  }
217
261
 
262
+ .router-section {
263
+ /* Container for router and categories */
264
+ }
265
+
218
266
  .categories {
219
267
  display: flex;
220
268
  flex-direction: row;
@@ -231,6 +279,11 @@ export class CanvasNode extends RapidElement {
231
279
  flex-direction: column;
232
280
  }
233
281
 
282
+ /* Localizable category - yellow background */
283
+ .category.localizable {
284
+ background-color: #fff8dc;
285
+ }
286
+
234
287
  .action-exits {
235
288
  padding-bottom: 0.7em;
236
289
  margin-top: -0.7em;
@@ -339,12 +392,79 @@ export class CanvasNode extends RapidElement {
339
392
  border-top-left-radius: var(--curvature);
340
393
  border-top-right-radius: var(--curvature);
341
394
  }
395
+
396
+ /* Add action button */
397
+ .add-action-button {
398
+ position: absolute;
399
+ bottom: 0.5em;
400
+ right: 0.5em;
401
+ width: 1.5em;
402
+ height: 1.5em;
403
+ border-radius: 50%;
404
+ background: var(--color-primary, #3b82f6);
405
+ color: white;
406
+ display: flex;
407
+ align-items: center;
408
+ justify-content: center;
409
+ cursor: pointer;
410
+ opacity: 0;
411
+ transition: opacity 200ms ease-in-out;
412
+ z-index: 10;
413
+ pointer-events: auto;
414
+ font-size: 0.9em;
415
+ }
416
+
417
+ .node.execute-actions:hover .add-action-button {
418
+ opacity: 0.8;
419
+ }
420
+
421
+ .add-action-button:hover {
422
+ opacity: 1 !important;
423
+ transform: scale(1.1);
424
+ }
342
425
  }`;
343
426
  }
344
427
 
345
428
  constructor() {
346
429
  super();
347
430
  this.handleActionOrderChanged = this.handleActionOrderChanged.bind(this);
431
+ this.handleActionDragStart = this.handleActionDragStart.bind(this);
432
+ this.handleActionDragExternal = this.handleActionDragExternal.bind(this);
433
+ this.handleActionDragInternal = this.handleActionDragInternal.bind(this);
434
+ this.handleActionDragStop = this.handleActionDragStop.bind(this);
435
+ this.handleExternalActionDragOver =
436
+ this.handleExternalActionDragOver.bind(this);
437
+ this.handleExternalActionDrop = this.handleExternalActionDrop.bind(this);
438
+ this.handleExternalActionDragLeave =
439
+ this.handleExternalActionDragLeave.bind(this);
440
+ this.handleActionShowGhost = this.handleActionShowGhost.bind(this);
441
+ this.handleActionHideGhost = this.handleActionHideGhost.bind(this);
442
+ }
443
+
444
+ connectedCallback() {
445
+ super.connectedCallback();
446
+
447
+ // Listen for external action drag events from Editor
448
+ this.addEventListener(
449
+ 'action-drag-over',
450
+ this.handleExternalActionDragOver as EventListener
451
+ );
452
+ this.addEventListener(
453
+ 'action-drop',
454
+ this.handleExternalActionDrop as EventListener
455
+ );
456
+ this.addEventListener(
457
+ 'action-drag-leave',
458
+ this.handleExternalActionDragLeave as EventListener
459
+ );
460
+ this.addEventListener(
461
+ 'action-show-ghost',
462
+ this.handleActionShowGhost as EventListener
463
+ );
464
+ this.addEventListener(
465
+ 'action-hide-ghost',
466
+ this.handleActionHideGhost as EventListener
467
+ );
348
468
  }
349
469
 
350
470
  protected updated(
@@ -391,6 +511,28 @@ export class CanvasNode extends RapidElement {
391
511
  // Remove the event listener when the component is removed
392
512
  super.disconnectedCallback();
393
513
 
514
+ // Remove external drag event listeners
515
+ this.removeEventListener(
516
+ 'action-drag-over',
517
+ this.handleExternalActionDragOver as EventListener
518
+ );
519
+ this.removeEventListener(
520
+ 'action-drop',
521
+ this.handleExternalActionDrop as EventListener
522
+ );
523
+ this.removeEventListener(
524
+ 'action-drag-leave',
525
+ this.handleExternalActionDragLeave as EventListener
526
+ );
527
+ this.removeEventListener(
528
+ 'action-show-ghost',
529
+ this.handleActionShowGhost as EventListener
530
+ );
531
+ this.removeEventListener(
532
+ 'action-hide-ghost',
533
+ this.handleActionHideGhost as EventListener
534
+ );
535
+
394
536
  // Clear any pending exit removal timeouts
395
537
  this.exitRemovalTimeouts.forEach((timeoutId) => {
396
538
  clearTimeout(timeoutId);
@@ -615,6 +757,82 @@ export class CanvasNode extends RapidElement {
615
757
  .updateNode(this.node.uuid, { ...this.node, actions: newActions });
616
758
  }
617
759
 
760
+ private handleActionDragStart(event: CustomEvent) {
761
+ // Capture the height of the action being dragged
762
+ const actionId = event.detail.id;
763
+ const actionElement = this.querySelector(`#${actionId}`) as HTMLElement;
764
+
765
+ if (actionElement) {
766
+ const rect = actionElement.getBoundingClientRect();
767
+ this.draggedActionHeight = rect.height;
768
+ } else {
769
+ // Fallback to a reasonable default
770
+ this.draggedActionHeight = 60;
771
+ }
772
+ }
773
+
774
+ private handleActionDragExternal(event: CustomEvent) {
775
+ // stop propagation of the original event from SortableList
776
+ event.stopPropagation();
777
+
778
+ // get the action being dragged
779
+ const actionId = event.detail.id;
780
+ const splitId = actionId.split('-');
781
+ if (splitId.length < 2 || isNaN(parseInt(splitId[1], 10))) {
782
+ // invalid format, do not proceed
783
+ return;
784
+ }
785
+ const actionIndex = parseInt(splitId[1], 10);
786
+ const action = this.node.actions[actionIndex];
787
+
788
+ // fire event to editor to show canvas drop preview, including the captured height
789
+ this.fireCustomEvent(CustomEventType.DragExternal, {
790
+ action,
791
+ nodeUuid: this.node.uuid,
792
+ actionIndex,
793
+ mouseX: event.detail.mouseX,
794
+ mouseY: event.detail.mouseY,
795
+ actionHeight: this.draggedActionHeight
796
+ });
797
+ }
798
+
799
+ private handleActionDragInternal(_event: CustomEvent) {
800
+ // stop propagation of the original event from SortableList
801
+ _event.stopPropagation();
802
+
803
+ // fire event to editor to hide canvas drop preview
804
+ this.fireCustomEvent(CustomEventType.DragInternal, {});
805
+ }
806
+
807
+ private handleActionDragStop(event: CustomEvent) {
808
+ const isExternal = event.detail.isExternal;
809
+
810
+ if (isExternal) {
811
+ // stop propagation of the original event from SortableList
812
+ event.stopPropagation();
813
+
814
+ // get the action being dragged
815
+ const actionId = event.detail.id;
816
+ const split = actionId.split('-');
817
+ if (split.length < 2 || isNaN(Number(split[1]))) {
818
+ // invalid actionId format, do not proceed
819
+ return;
820
+ }
821
+ const actionIndex = parseInt(split[1], 10);
822
+ const action = this.node.actions[actionIndex];
823
+
824
+ // fire event to editor to create new node
825
+ this.fireCustomEvent(CustomEventType.DragStop, {
826
+ action,
827
+ nodeUuid: this.node.uuid,
828
+ actionIndex,
829
+ isExternal: true,
830
+ mouseX: event.detail.mouseX,
831
+ mouseY: event.detail.mouseY
832
+ });
833
+ }
834
+ }
835
+
618
836
  private handleActionMouseDown(event: MouseEvent, action: Action): void {
619
837
  // Don't handle clicks on the remove button, drag handle, or when action is in removing state
620
838
  const target = event.target as HTMLElement;
@@ -813,65 +1031,316 @@ export class CanvasNode extends RapidElement {
813
1031
  this.pendingNodeClick = null;
814
1032
  }
815
1033
 
1034
+ private handleAddActionClick(event: MouseEvent): void {
1035
+ event.preventDefault();
1036
+ event.stopPropagation();
1037
+
1038
+ // Fire event to request adding a new action to this node
1039
+ this.fireCustomEvent(CustomEventType.AddActionRequested, {
1040
+ nodeUuid: this.node.uuid
1041
+ });
1042
+ }
1043
+
1044
+ private calculateDropIndex(mouseY: number): number {
1045
+ // Get the sortable list element
1046
+ const sortableList = this.querySelector('temba-sortable-list');
1047
+ if (!sortableList || !this.node.actions)
1048
+ return this.node.actions?.length ?? 0;
1049
+
1050
+ // Get all action elements
1051
+ const actionElements = Array.from(
1052
+ sortableList.querySelectorAll('.action.sortable')
1053
+ );
1054
+
1055
+ if (actionElements.length === 0) {
1056
+ return 0;
1057
+ }
1058
+
1059
+ // Find where to insert based on mouse Y position
1060
+ for (let i = 0; i < actionElements.length; i++) {
1061
+ const actionElement = actionElements[i] as HTMLElement;
1062
+ const rect = actionElement.getBoundingClientRect();
1063
+ const centerY = rect.top + rect.height / 2;
1064
+
1065
+ if (mouseY < centerY) {
1066
+ return i;
1067
+ }
1068
+ }
1069
+
1070
+ // If past all elements, insert at the end
1071
+ return actionElements.length;
1072
+ }
1073
+
1074
+ private handleExternalActionDragOver(event: CustomEvent): void {
1075
+ // Only handle if this is an execute_actions node
1076
+ if (this.ui.type !== 'execute_actions') return;
1077
+
1078
+ const { action, sourceNodeUuid, actionIndex, mouseY, actionHeight } =
1079
+ event.detail;
1080
+
1081
+ // Don't accept drops from the same node
1082
+ if (sourceNodeUuid === this.node.uuid) return;
1083
+
1084
+ // Calculate where to drop
1085
+ const dropIndex = this.calculateDropIndex(mouseY);
1086
+
1087
+ // Store the drag info
1088
+ this.externalDragInfo = {
1089
+ action,
1090
+ sourceNodeUuid,
1091
+ actionIndex,
1092
+ dropIndex,
1093
+ actionHeight: actionHeight || 60 // fallback to 60px if not provided
1094
+ };
1095
+
1096
+ // Request update to show placeholder
1097
+ this.requestUpdate();
1098
+ }
1099
+
1100
+ private handleExternalActionDragLeave(_event: CustomEvent): void {
1101
+ // Clear external drag state when drag leaves this node
1102
+ this.externalDragInfo = null;
1103
+ this.requestUpdate();
1104
+ }
1105
+
1106
+ private handleActionShowGhost(_event: CustomEvent): void {
1107
+ // Show the ghost element in the sortable list
1108
+ const sortableList = this.querySelector('temba-sortable-list');
1109
+ if (sortableList) {
1110
+ const ghostElement = document.querySelector('.ghost') as HTMLElement;
1111
+ if (ghostElement) {
1112
+ ghostElement.style.display = 'block';
1113
+ }
1114
+ }
1115
+ }
1116
+
1117
+ private handleActionHideGhost(_event: CustomEvent): void {
1118
+ // Hide the ghost element in the sortable list
1119
+ const sortableList = this.querySelector('temba-sortable-list');
1120
+ if (sortableList) {
1121
+ const ghostElement = document.querySelector('.ghost') as HTMLElement;
1122
+ if (ghostElement) {
1123
+ ghostElement.style.display = 'none';
1124
+ }
1125
+ }
1126
+ }
1127
+
1128
+ private handleExternalActionDrop(event: CustomEvent): void {
1129
+ // Only handle if this is an execute_actions node
1130
+ if (this.ui.type !== 'execute_actions') return;
1131
+
1132
+ const { action, sourceNodeUuid, actionIndex } = event.detail;
1133
+
1134
+ // Don't accept drops from the same node
1135
+ if (sourceNodeUuid === this.node.uuid) return;
1136
+
1137
+ // Get the drop index from our tracking state
1138
+ const dropIndex =
1139
+ this.externalDragInfo?.dropIndex ?? this.node.actions?.length ?? 0;
1140
+
1141
+ // Clear external drag state
1142
+ this.externalDragInfo = null;
1143
+
1144
+ // Remove the action from the source node
1145
+ const store = getStore();
1146
+ if (!store) return;
1147
+
1148
+ const flowDefinition = store.getState().flowDefinition;
1149
+ if (!flowDefinition) return;
1150
+
1151
+ const sourceNode = flowDefinition.nodes.find(
1152
+ (n) => n.uuid === sourceNodeUuid
1153
+ );
1154
+
1155
+ if (sourceNode) {
1156
+ const updatedSourceActions = sourceNode.actions.filter(
1157
+ (_a, idx) => idx !== actionIndex
1158
+ );
1159
+
1160
+ // If source node has no actions left, remove it
1161
+ if (updatedSourceActions.length === 0) {
1162
+ this.fireCustomEvent(CustomEventType.NodeDeleted, {
1163
+ uuid: sourceNodeUuid
1164
+ });
1165
+ } else {
1166
+ // Update source node
1167
+ const updatedSourceNode = {
1168
+ ...sourceNode,
1169
+ actions: updatedSourceActions
1170
+ };
1171
+ getStore()?.getState().updateNode(sourceNodeUuid, updatedSourceNode);
1172
+ }
1173
+ }
1174
+
1175
+ // Add the action to this node at the calculated position
1176
+ const newActions = [...this.node.actions];
1177
+ newActions.splice(dropIndex, 0, action);
1178
+
1179
+ const updatedNode = { ...this.node, actions: newActions };
1180
+ getStore()?.getState().updateNode(this.node.uuid, updatedNode);
1181
+
1182
+ // Request update
1183
+ this.requestUpdate();
1184
+ }
1185
+
816
1186
  private renderTitle(
817
1187
  config: ActionConfig,
818
1188
  action: Action,
819
1189
  index: number,
820
1190
  isRemoving: boolean = false
821
1191
  ) {
822
- return html`<div class="cn-title" style="background:${config.color}">
823
- ${this.node?.actions?.length > 1
1192
+ const color = config.group
1193
+ ? ACTION_GROUP_METADATA[config.group]?.color
1194
+ : '#aaaaaa';
1195
+ return html`<div class="cn-title" style="background:${color}">
1196
+ ${!this.isTranslating && this.node?.actions?.length > 1
824
1197
  ? html`<temba-icon class="drag-handle" name="sort"></temba-icon>`
825
- : null}
1198
+ : html`<div class="title-spacer"></div>`}
826
1199
 
827
1200
  <div class="name">${isRemoving ? 'Remove?' : config.name}</div>
828
- <div
829
- class="remove-button"
830
- @click=${(e: MouseEvent) =>
831
- this.handleActionRemoveClick(e, action, index)}
832
- title="Remove action"
833
- >
834
-
835
- </div>
1201
+ ${!this.isTranslating
1202
+ ? html`<div
1203
+ class="remove-button"
1204
+ @click=${(e: MouseEvent) =>
1205
+ this.handleActionRemoveClick(e, action, index)}
1206
+ title="Remove action"
1207
+ >
1208
+
1209
+ </div>`
1210
+ : html`<div class="title-spacer"></div>`}
836
1211
  </div>`;
837
1212
  }
838
1213
 
839
- private renderNodeTitle(config: NodeConfig, isRemoving: boolean = false) {
1214
+ private renderNodeTitle(
1215
+ config: NodeConfig,
1216
+ node: Node,
1217
+ ui: NodeUI,
1218
+ isRemoving: boolean = false
1219
+ ) {
1220
+ // Get color from the appropriate metadata (either ACTION or SPLIT)
1221
+ const color = config.group
1222
+ ? ACTION_GROUP_METADATA[config.group]?.color ||
1223
+ SPLIT_GROUP_METADATA[config.group]?.color
1224
+ : '#aaaaaa';
840
1225
  return html`<div
841
1226
  class="cn-title ${isRemoving ? 'removing' : ''}"
842
- style="background:${config.color}"
1227
+ style="background:${color}"
843
1228
  >
844
1229
  <div class="title-spacer"></div>
845
- <div class="name">${isRemoving ? 'Remove?' : config.name}</div>
846
- <div
847
- class="remove-button"
848
- @click=${(e: MouseEvent) => this.handleNodeRemoveClick(e)}
849
- title="Remove node"
850
- >
851
-
1230
+ <div class="name">
1231
+ ${isRemoving
1232
+ ? 'Remove?'
1233
+ : config.renderTitle
1234
+ ? config.renderTitle(node, ui)
1235
+ : html`${config.name}`}
852
1236
  </div>
1237
+ ${!this.isTranslating
1238
+ ? html`<div
1239
+ class="remove-button"
1240
+ @click=${(e: MouseEvent) => this.handleNodeRemoveClick(e)}
1241
+ title="Remove node"
1242
+ >
1243
+
1244
+ </div>`
1245
+ : html`<div class="title-spacer"></div>`}
853
1246
  </div>`;
854
1247
  }
855
1248
 
1249
+ private renderDropPlaceholder() {
1250
+ const height = this.externalDragInfo?.actionHeight || 60;
1251
+ return html`<div
1252
+ class="action sortable drop-placeholder"
1253
+ style="height: ${height}px; background: #f3f4f6; border: 2px dashed #d1d5db; border-radius: var(--curvature);"
1254
+ ></div>`;
1255
+ }
1256
+
1257
+ /**
1258
+ * Get the localized version of an action if translating, otherwise return the original action.
1259
+ * Falls back to base language values if no localization exists for a field.
1260
+ */
1261
+ private getLocalizedAction(action: Action): Action {
1262
+ // If not translating or no flow definition, return original action
1263
+ if (
1264
+ !this.isTranslating ||
1265
+ !this.flowDefinition ||
1266
+ !this.languageCode ||
1267
+ this.languageCode === this.flowDefinition.language
1268
+ ) {
1269
+ return action;
1270
+ }
1271
+
1272
+ // Check if there's localization for this action
1273
+ const localization =
1274
+ this.flowDefinition?.localization?.[this.languageCode]?.[action.uuid];
1275
+
1276
+ if (!localization) {
1277
+ // No localization available, return original action
1278
+ return action;
1279
+ }
1280
+
1281
+ // Create a new action with localized values, falling back to base language
1282
+ const localizedAction = { ...action };
1283
+
1284
+ // Apply localized values for each field
1285
+ Object.keys(localization).forEach((field) => {
1286
+ const localizedValue = localization[field];
1287
+ if (Array.isArray(localizedValue)) {
1288
+ // Localized values are stored as arrays
1289
+ if (localizedValue.length > 0) {
1290
+ // For single-value fields like 'text', take the first element
1291
+ // For array fields like 'quick_replies', use the whole array
1292
+ if (Array.isArray(action[field])) {
1293
+ localizedAction[field] = localizedValue;
1294
+ } else {
1295
+ localizedAction[field] = localizedValue[0];
1296
+ }
1297
+ }
1298
+ }
1299
+ });
1300
+
1301
+ return localizedAction;
1302
+ }
1303
+
856
1304
  private renderAction(node: Node, action: Action, index: number) {
857
1305
  const config = ACTION_CONFIG[action.type];
858
1306
  const isRemoving = this.actionRemovingState.has(action.uuid);
1307
+ const isLocalizable = config?.localizable && config.localizable.length > 0;
1308
+ const isDisabled = this.isTranslating && !isLocalizable;
1309
+
1310
+ // Check if this action has localization data
1311
+ const hasLocalization =
1312
+ this.isTranslating &&
1313
+ this.flowDefinition?.localization?.[this.languageCode]?.[action.uuid];
1314
+
1315
+ // Get the localized action if translating
1316
+ const displayAction = this.getLocalizedAction(action);
859
1317
 
860
1318
  if (config) {
861
- return html`<div
862
- class="action sortable ${action.type} ${isRemoving ? 'removing' : ''}"
863
- id="action-${index}"
864
- >
1319
+ const classes = [
1320
+ 'action',
1321
+ 'sortable',
1322
+ action.type,
1323
+ isRemoving ? 'removing' : '',
1324
+ isLocalizable && this.isTranslating ? 'localizable' : '',
1325
+ hasLocalization ? 'has-localization' : '',
1326
+ isDisabled ? 'non-localizable' : ''
1327
+ ]
1328
+ .filter(Boolean)
1329
+ .join(' ');
1330
+
1331
+ return html`<div class="${classes}" id="action-${index}">
865
1332
  <div
866
1333
  class="action-content"
867
- @mousedown=${(e: MouseEvent) => this.handleActionMouseDown(e, action)}
868
- @mouseup=${(e: MouseEvent) => this.handleActionMouseUp(e, action)}
869
- style="cursor: pointer; background: #fff"
1334
+ @mousedown=${(e: MouseEvent) =>
1335
+ !isDisabled && this.handleActionMouseDown(e, action)}
1336
+ @mouseup=${(e: MouseEvent) =>
1337
+ !isDisabled && this.handleActionMouseUp(e, action)}
1338
+ style="cursor: ${isDisabled ? 'not-allowed' : 'pointer'}"
870
1339
  >
871
1340
  ${this.renderTitle(config, action, index, isRemoving)}
872
1341
  <div class="body">
873
1342
  ${config.render
874
- ? config.render(node, action)
1343
+ ? config.render(node, displayAction)
875
1344
  : html`<pre>${action.type}</pre>`}
876
1345
  </div>
877
1346
  </div>
@@ -893,6 +1362,31 @@ export class CanvasNode extends RapidElement {
893
1362
  </div>`;
894
1363
  }
895
1364
 
1365
+ private renderActionsWithPlaceholder() {
1366
+ if (!this.externalDragInfo) {
1367
+ // No external drag, render normally
1368
+ return this.node.actions.map((action, index) =>
1369
+ this.renderAction(this.node, action, index)
1370
+ );
1371
+ }
1372
+
1373
+ // Insert placeholder at the drop index
1374
+ const result = [];
1375
+ for (let i = 0; i < this.node.actions.length; i++) {
1376
+ if (i === this.externalDragInfo.dropIndex) {
1377
+ result.push(this.renderDropPlaceholder());
1378
+ }
1379
+ result.push(this.renderAction(this.node, this.node.actions[i], i));
1380
+ }
1381
+
1382
+ // If dropping at the end, add placeholder after all actions
1383
+ if (this.externalDragInfo.dropIndex >= this.node.actions.length) {
1384
+ result.push(this.renderDropPlaceholder());
1385
+ }
1386
+
1387
+ return result;
1388
+ }
1389
+
896
1390
  private renderRouter(router: Router, ui: NodeUI) {
897
1391
  const nodeConfig = NODE_CONFIG[ui.type];
898
1392
  if (nodeConfig) {
@@ -917,6 +1411,10 @@ export class CanvasNode extends RapidElement {
917
1411
  return null;
918
1412
  }
919
1413
 
1414
+ // Check if this node type supports category localization
1415
+ const nodeConfig = NODE_CONFIG[this.ui?.type];
1416
+ const supportsLocalization = nodeConfig?.localizable === 'categories';
1417
+
920
1418
  return html`<div class="categories">
921
1419
  ${repeat(
922
1420
  node.router.categories,
@@ -926,13 +1424,44 @@ export class CanvasNode extends RapidElement {
926
1424
  (exit: Exit) => exit.uuid == category.exit_uuid
927
1425
  );
928
1426
 
1427
+ // Get localized category name if translating
1428
+ let displayName = category.name;
1429
+ let isLocalized = false;
1430
+
1431
+ if (
1432
+ this.isTranslating &&
1433
+ this.languageCode !== 'eng' &&
1434
+ supportsLocalization
1435
+ ) {
1436
+ const localization =
1437
+ this.flowDefinition?.localization?.[this.languageCode];
1438
+ if (localization && localization[category.uuid]) {
1439
+ const categoryLocalization = localization[category.uuid];
1440
+ if (categoryLocalization.name && categoryLocalization.name[0]) {
1441
+ displayName = categoryLocalization.name[0];
1442
+ isLocalized = true;
1443
+ }
1444
+ }
1445
+ }
1446
+
1447
+ // Category is localizable if: translating, supports localization, categories enabled, and not base language
1448
+ const isLocalizable =
1449
+ this.isTranslating &&
1450
+ this.languageCode !== 'eng' &&
1451
+ supportsLocalization &&
1452
+ this.includeCategoriesInTranslation &&
1453
+ !isLocalized;
1454
+
929
1455
  return html`<div
930
- class="category"
1456
+ class=${getClasses({
1457
+ category: true,
1458
+ localizable: isLocalizable
1459
+ })}
931
1460
  @mousedown=${(e: MouseEvent) => this.handleNodeMouseDown(e)}
932
1461
  @mouseup=${(e: MouseEvent) => this.handleNodeMouseUp(e)}
933
1462
  style="cursor: pointer;"
934
1463
  >
935
- <div class="cn-title">${category.name}</div>
1464
+ <div class="cn-title">${displayName}</div>
936
1465
  ${this.renderExit(exit)}
937
1466
  </div>`;
938
1467
  }
@@ -961,12 +1490,21 @@ export class CanvasNode extends RapidElement {
961
1490
 
962
1491
  const nodeConfig = NODE_CONFIG[this.ui.type];
963
1492
 
1493
+ // Check if this node should be disabled (grayed out)
1494
+ const supportsLocalization = nodeConfig?.localizable === 'categories';
1495
+ const isNodeDisabled =
1496
+ this.isTranslating &&
1497
+ supportsLocalization &&
1498
+ !this.includeCategoriesInTranslation;
1499
+
964
1500
  return html`
965
1501
  <div
966
1502
  id="${this.node.uuid}"
967
- class="node ${this.ui.type === 'execute_actions'
968
- ? 'execute-actions'
969
- : ''}"
1503
+ class=${getClasses({
1504
+ node: true,
1505
+ 'execute-actions': this.ui.type === 'execute_actions',
1506
+ 'non-localizable': isNodeDisabled
1507
+ })}
970
1508
  style="left:${this.ui.position.left}px;top:${this.ui.position.top}px"
971
1509
  >
972
1510
  ${nodeConfig && nodeConfig.type !== 'execute_actions'
@@ -978,28 +1516,37 @@ export class CanvasNode extends RapidElement {
978
1516
  >
979
1517
  ${this.renderNodeTitle(
980
1518
  nodeConfig,
1519
+ this.node,
1520
+ this.ui,
981
1521
  this.actionRemovingState.has(this.node.uuid)
982
1522
  )}
983
- ${nodeConfig.render ? nodeConfig.render(this.node) : null}
1523
+ ${nodeConfig.render
1524
+ ? nodeConfig.render(this.node, this.ui)
1525
+ : null}
984
1526
  </div>
985
1527
  </div>`
986
1528
  : this.node.actions.length > 0
987
1529
  ? this.ui.type === 'execute_actions'
988
1530
  ? html`<temba-sortable-list
989
1531
  dragHandle="drag-handle"
1532
+ externalDrag
990
1533
  @temba-order-changed="${this.handleActionOrderChanged}"
1534
+ @temba-drag-start="${this.handleActionDragStart}"
1535
+ @temba-drag-external="${this.handleActionDragExternal}"
1536
+ @temba-drag-internal="${this.handleActionDragInternal}"
1537
+ @temba-drag-stop="${this.handleActionDragStop}"
991
1538
  >
992
- ${this.node.actions.map((action, index) =>
993
- this.renderAction(this.node, action, index)
994
- )}
1539
+ ${this.renderActionsWithPlaceholder()}
995
1540
  </temba-sortable-list>`
996
1541
  : html`${this.node.actions.map((action, index) =>
997
1542
  this.renderAction(this.node, action, index)
998
1543
  )}`
999
1544
  : ''}
1000
1545
  ${this.node.router
1001
- ? html` ${this.renderRouter(this.node.router, this.ui)}
1002
- ${this.renderCategories(this.node)}`
1546
+ ? html`<div class="router-section">
1547
+ ${this.renderRouter(this.node.router, this.ui)}
1548
+ ${this.renderCategories(this.node)}
1549
+ </div>`
1003
1550
  : html`<div class="action-exits">
1004
1551
  ${repeat(
1005
1552
  this.node.exits,
@@ -1007,6 +1554,15 @@ export class CanvasNode extends RapidElement {
1007
1554
  (exit) => this.renderExit(exit)
1008
1555
  )}
1009
1556
  </div>`}
1557
+ ${this.ui.type === 'execute_actions' && !this.isTranslating
1558
+ ? html`<div
1559
+ class="add-action-button"
1560
+ @click=${(e: MouseEvent) => this.handleAddActionClick(e)}
1561
+ title="Add action"
1562
+ >
1563
+ <temba-icon name="add"></temba-icon>
1564
+ </div>`
1565
+ : ''}
1010
1566
  </div>
1011
1567
  `;
1012
1568
  }