@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
@@ -2,11 +2,13 @@ import { __decorate } from "tslib";
2
2
  import { css, html } from 'lit';
3
3
  import { repeat } from 'lit/directives/repeat.js';
4
4
  import { ACTION_CONFIG, NODE_CONFIG } from './config';
5
+ import { ACTION_GROUP_METADATA, SPLIT_GROUP_METADATA } from './types';
5
6
  import { property } from 'lit/decorators.js';
6
7
  import { RapidElement } from '../RapidElement';
7
8
  import { getClasses } from '../utils';
8
9
  import { getStore } from '../store/Store';
9
10
  import { CustomEventType } from '../interfaces';
11
+ import { fromStore, zustand } from '../store/AppState';
10
12
  const DRAG_THRESHOLD = 5;
11
13
  export class CanvasNode extends RapidElement {
12
14
  createRenderRoot() {
@@ -113,6 +115,20 @@ export class CanvasNode extends RapidElement {
113
115
 
114
116
  .node.execute-actions temba-sortable-list .action:last-child .body {
115
117
  padding-bottom: 1.5em;
118
+ }
119
+
120
+ /* Localization indicators */
121
+ .action.localizable:not(.has-localization) .action-content {
122
+ background: #fff8dc !important; /* Light yellow background for localizable but not yet localized */
123
+ }
124
+
125
+ .non-localizable {
126
+ opacity: 0.25;
127
+ pointer-events: none;
128
+ }
129
+
130
+ .action.non-localizable .action-content {
131
+ cursor: not-allowed;
116
132
  }
117
133
 
118
134
  .action .drag-handle {
@@ -126,7 +142,7 @@ export class CanvasNode extends RapidElement {
126
142
  pointer-events: auto; /* Ensure drag handle can receive events */
127
143
  }
128
144
  .title-spacer {
129
- width: 2em;
145
+ width: 1.8em;
130
146
 
131
147
  }
132
148
 
@@ -181,6 +197,10 @@ export class CanvasNode extends RapidElement {
181
197
  margin: 0.2em;
182
198
  }
183
199
 
200
+ .router-section {
201
+ /* Container for router and categories */
202
+ }
203
+
184
204
  .categories {
185
205
  display: flex;
186
206
  flex-direction: row;
@@ -197,6 +217,11 @@ export class CanvasNode extends RapidElement {
197
217
  flex-direction: column;
198
218
  }
199
219
 
220
+ /* Localizable category - yellow background */
221
+ .category.localizable {
222
+ background-color: #fff8dc;
223
+ }
224
+
200
225
  .action-exits {
201
226
  padding-bottom: 0.7em;
202
227
  margin-top: -0.7em;
@@ -305,6 +330,36 @@ export class CanvasNode extends RapidElement {
305
330
  border-top-left-radius: var(--curvature);
306
331
  border-top-right-radius: var(--curvature);
307
332
  }
333
+
334
+ /* Add action button */
335
+ .add-action-button {
336
+ position: absolute;
337
+ bottom: 0.5em;
338
+ right: 0.5em;
339
+ width: 1.5em;
340
+ height: 1.5em;
341
+ border-radius: 50%;
342
+ background: var(--color-primary, #3b82f6);
343
+ color: white;
344
+ display: flex;
345
+ align-items: center;
346
+ justify-content: center;
347
+ cursor: pointer;
348
+ opacity: 0;
349
+ transition: opacity 200ms ease-in-out;
350
+ z-index: 10;
351
+ pointer-events: auto;
352
+ font-size: 0.9em;
353
+ }
354
+
355
+ .node.execute-actions:hover .add-action-button {
356
+ opacity: 0.8;
357
+ }
358
+
359
+ .add-action-button:hover {
360
+ opacity: 1 !important;
361
+ transform: scale(1.1);
362
+ }
308
363
  }`;
309
364
  }
310
365
  constructor() {
@@ -323,10 +378,34 @@ export class CanvasNode extends RapidElement {
323
378
  // Track node click state to distinguish from drag
324
379
  this.nodeClickStartPos = null;
325
380
  this.pendingNodeClick = null;
381
+ // Track the height of the action being dragged (captured at drag start)
382
+ this.draggedActionHeight = 0;
383
+ // Track external action drag (action being dragged from another node)
384
+ this.externalDragInfo = null;
326
385
  this.handleActionOrderChanged = this.handleActionOrderChanged.bind(this);
386
+ this.handleActionDragStart = this.handleActionDragStart.bind(this);
387
+ this.handleActionDragExternal = this.handleActionDragExternal.bind(this);
388
+ this.handleActionDragInternal = this.handleActionDragInternal.bind(this);
389
+ this.handleActionDragStop = this.handleActionDragStop.bind(this);
390
+ this.handleExternalActionDragOver =
391
+ this.handleExternalActionDragOver.bind(this);
392
+ this.handleExternalActionDrop = this.handleExternalActionDrop.bind(this);
393
+ this.handleExternalActionDragLeave =
394
+ this.handleExternalActionDragLeave.bind(this);
395
+ this.handleActionShowGhost = this.handleActionShowGhost.bind(this);
396
+ this.handleActionHideGhost = this.handleActionHideGhost.bind(this);
397
+ }
398
+ connectedCallback() {
399
+ super.connectedCallback();
400
+ // Listen for external action drag events from Editor
401
+ this.addEventListener('action-drag-over', this.handleExternalActionDragOver);
402
+ this.addEventListener('action-drop', this.handleExternalActionDrop);
403
+ this.addEventListener('action-drag-leave', this.handleExternalActionDragLeave);
404
+ this.addEventListener('action-show-ghost', this.handleActionShowGhost);
405
+ this.addEventListener('action-hide-ghost', this.handleActionHideGhost);
327
406
  }
328
407
  updated(changes) {
329
- var _a;
408
+ var _b;
330
409
  super.updated(changes);
331
410
  if (changes.has('node')) {
332
411
  // Only proceed if plumber is available (for tests that don't set it up)
@@ -348,13 +427,19 @@ export class CanvasNode extends RapidElement {
348
427
  const ele = this.parentElement;
349
428
  if (ele) {
350
429
  const rect = ele.getBoundingClientRect();
351
- (_a = getStore()) === null || _a === void 0 ? void 0 : _a.getState().expandCanvas(this.ui.position.left + rect.width, this.ui.position.top + rect.height);
430
+ (_b = getStore()) === null || _b === void 0 ? void 0 : _b.getState().expandCanvas(this.ui.position.left + rect.width, this.ui.position.top + rect.height);
352
431
  }
353
432
  }
354
433
  }
355
434
  disconnectedCallback() {
356
435
  // Remove the event listener when the component is removed
357
436
  super.disconnectedCallback();
437
+ // Remove external drag event listeners
438
+ this.removeEventListener('action-drag-over', this.handleExternalActionDragOver);
439
+ this.removeEventListener('action-drop', this.handleExternalActionDrop);
440
+ this.removeEventListener('action-drag-leave', this.handleExternalActionDragLeave);
441
+ this.removeEventListener('action-show-ghost', this.handleActionShowGhost);
442
+ this.removeEventListener('action-hide-ghost', this.handleActionHideGhost);
358
443
  // Clear any pending exit removal timeouts
359
444
  this.exitRemovalTimeouts.forEach((timeoutId) => {
360
445
  clearTimeout(timeoutId);
@@ -401,7 +486,7 @@ export class CanvasNode extends RapidElement {
401
486
  this.exitRemovalTimeouts.set(exitId, timeoutId);
402
487
  }
403
488
  disconnectExit(exit) {
404
- var _a;
489
+ var _b;
405
490
  const exitId = exit.uuid;
406
491
  // Clear the UI state
407
492
  this.exitRemovingState.delete(exitId);
@@ -420,7 +505,7 @@ export class CanvasNode extends RapidElement {
420
505
  const updatedExits = this.node.exits.map((e) => e.uuid === exitId ? updatedExit : e);
421
506
  // Update the node
422
507
  const updatedNode = { ...this.node, exits: updatedExits };
423
- (_a = getStore()) === null || _a === void 0 ? void 0 : _a.getState().updateNode(this.node.uuid, updatedNode);
508
+ (_b = getStore()) === null || _b === void 0 ? void 0 : _b.getState().updateNode(this.node.uuid, updatedNode);
424
509
  // Request update to reflect changes
425
510
  this.requestUpdate();
426
511
  }
@@ -450,7 +535,7 @@ export class CanvasNode extends RapidElement {
450
535
  }
451
536
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
452
537
  removeAction(action, _index) {
453
- var _a;
538
+ var _b;
454
539
  const actionId = action.uuid;
455
540
  // Clear the UI state
456
541
  this.actionRemovingState.delete(actionId);
@@ -470,7 +555,7 @@ export class CanvasNode extends RapidElement {
470
555
  else {
471
556
  // Update the node with remaining actions
472
557
  const updatedNode = { ...this.node, actions: updatedActions };
473
- (_a = getStore()) === null || _a === void 0 ? void 0 : _a.getState().updateNode(this.node.uuid, updatedNode);
558
+ (_b = getStore()) === null || _b === void 0 ? void 0 : _b.getState().updateNode(this.node.uuid, updatedNode);
474
559
  // Request update to reflect changes
475
560
  this.requestUpdate();
476
561
  }
@@ -514,7 +599,7 @@ export class CanvasNode extends RapidElement {
514
599
  });
515
600
  }
516
601
  handleActionOrderChanged(event) {
517
- var _a;
602
+ var _b;
518
603
  const [fromIdx, toIdx] = event.detail.swap;
519
604
  // swap our actions
520
605
  const newActions = [...this.node.actions];
@@ -524,7 +609,73 @@ export class CanvasNode extends RapidElement {
524
609
  // since the editor will update us from it's definition subscription
525
610
  // but it makes testing a lot easier
526
611
  this.node = { ...this.node, actions: newActions };
527
- (_a = getStore()) === null || _a === void 0 ? void 0 : _a.getState().updateNode(this.node.uuid, { ...this.node, actions: newActions });
612
+ (_b = getStore()) === null || _b === void 0 ? void 0 : _b.getState().updateNode(this.node.uuid, { ...this.node, actions: newActions });
613
+ }
614
+ handleActionDragStart(event) {
615
+ // Capture the height of the action being dragged
616
+ const actionId = event.detail.id;
617
+ const actionElement = this.querySelector(`#${actionId}`);
618
+ if (actionElement) {
619
+ const rect = actionElement.getBoundingClientRect();
620
+ this.draggedActionHeight = rect.height;
621
+ }
622
+ else {
623
+ // Fallback to a reasonable default
624
+ this.draggedActionHeight = 60;
625
+ }
626
+ }
627
+ handleActionDragExternal(event) {
628
+ // stop propagation of the original event from SortableList
629
+ event.stopPropagation();
630
+ // get the action being dragged
631
+ const actionId = event.detail.id;
632
+ const splitId = actionId.split('-');
633
+ if (splitId.length < 2 || isNaN(parseInt(splitId[1], 10))) {
634
+ // invalid format, do not proceed
635
+ return;
636
+ }
637
+ const actionIndex = parseInt(splitId[1], 10);
638
+ const action = this.node.actions[actionIndex];
639
+ // fire event to editor to show canvas drop preview, including the captured height
640
+ this.fireCustomEvent(CustomEventType.DragExternal, {
641
+ action,
642
+ nodeUuid: this.node.uuid,
643
+ actionIndex,
644
+ mouseX: event.detail.mouseX,
645
+ mouseY: event.detail.mouseY,
646
+ actionHeight: this.draggedActionHeight
647
+ });
648
+ }
649
+ handleActionDragInternal(_event) {
650
+ // stop propagation of the original event from SortableList
651
+ _event.stopPropagation();
652
+ // fire event to editor to hide canvas drop preview
653
+ this.fireCustomEvent(CustomEventType.DragInternal, {});
654
+ }
655
+ handleActionDragStop(event) {
656
+ const isExternal = event.detail.isExternal;
657
+ if (isExternal) {
658
+ // stop propagation of the original event from SortableList
659
+ event.stopPropagation();
660
+ // get the action being dragged
661
+ const actionId = event.detail.id;
662
+ const split = actionId.split('-');
663
+ if (split.length < 2 || isNaN(Number(split[1]))) {
664
+ // invalid actionId format, do not proceed
665
+ return;
666
+ }
667
+ const actionIndex = parseInt(split[1], 10);
668
+ const action = this.node.actions[actionIndex];
669
+ // fire event to editor to create new node
670
+ this.fireCustomEvent(CustomEventType.DragStop, {
671
+ action,
672
+ nodeUuid: this.node.uuid,
673
+ actionIndex,
674
+ isExternal: true,
675
+ mouseX: event.detail.mouseX,
676
+ mouseY: event.detail.mouseY
677
+ });
678
+ }
528
679
  }
529
680
  handleActionMouseDown(event, action) {
530
681
  // Don't handle clicks on the remove button, drag handle, or when action is in removing state
@@ -690,57 +841,263 @@ export class CanvasNode extends RapidElement {
690
841
  this.nodeClickStartPos = null;
691
842
  this.pendingNodeClick = null;
692
843
  }
844
+ handleAddActionClick(event) {
845
+ event.preventDefault();
846
+ event.stopPropagation();
847
+ // Fire event to request adding a new action to this node
848
+ this.fireCustomEvent(CustomEventType.AddActionRequested, {
849
+ nodeUuid: this.node.uuid
850
+ });
851
+ }
852
+ calculateDropIndex(mouseY) {
853
+ var _b, _c;
854
+ // Get the sortable list element
855
+ const sortableList = this.querySelector('temba-sortable-list');
856
+ if (!sortableList || !this.node.actions)
857
+ return (_c = (_b = this.node.actions) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0;
858
+ // Get all action elements
859
+ const actionElements = Array.from(sortableList.querySelectorAll('.action.sortable'));
860
+ if (actionElements.length === 0) {
861
+ return 0;
862
+ }
863
+ // Find where to insert based on mouse Y position
864
+ for (let i = 0; i < actionElements.length; i++) {
865
+ const actionElement = actionElements[i];
866
+ const rect = actionElement.getBoundingClientRect();
867
+ const centerY = rect.top + rect.height / 2;
868
+ if (mouseY < centerY) {
869
+ return i;
870
+ }
871
+ }
872
+ // If past all elements, insert at the end
873
+ return actionElements.length;
874
+ }
875
+ handleExternalActionDragOver(event) {
876
+ // Only handle if this is an execute_actions node
877
+ if (this.ui.type !== 'execute_actions')
878
+ return;
879
+ const { action, sourceNodeUuid, actionIndex, mouseY, actionHeight } = event.detail;
880
+ // Don't accept drops from the same node
881
+ if (sourceNodeUuid === this.node.uuid)
882
+ return;
883
+ // Calculate where to drop
884
+ const dropIndex = this.calculateDropIndex(mouseY);
885
+ // Store the drag info
886
+ this.externalDragInfo = {
887
+ action,
888
+ sourceNodeUuid,
889
+ actionIndex,
890
+ dropIndex,
891
+ actionHeight: actionHeight || 60 // fallback to 60px if not provided
892
+ };
893
+ // Request update to show placeholder
894
+ this.requestUpdate();
895
+ }
896
+ handleExternalActionDragLeave(_event) {
897
+ // Clear external drag state when drag leaves this node
898
+ this.externalDragInfo = null;
899
+ this.requestUpdate();
900
+ }
901
+ handleActionShowGhost(_event) {
902
+ // Show the ghost element in the sortable list
903
+ const sortableList = this.querySelector('temba-sortable-list');
904
+ if (sortableList) {
905
+ const ghostElement = document.querySelector('.ghost');
906
+ if (ghostElement) {
907
+ ghostElement.style.display = 'block';
908
+ }
909
+ }
910
+ }
911
+ handleActionHideGhost(_event) {
912
+ // Hide the ghost element in the sortable list
913
+ const sortableList = this.querySelector('temba-sortable-list');
914
+ if (sortableList) {
915
+ const ghostElement = document.querySelector('.ghost');
916
+ if (ghostElement) {
917
+ ghostElement.style.display = 'none';
918
+ }
919
+ }
920
+ }
921
+ handleExternalActionDrop(event) {
922
+ var _b, _c, _d, _e, _f, _g;
923
+ // Only handle if this is an execute_actions node
924
+ if (this.ui.type !== 'execute_actions')
925
+ return;
926
+ const { action, sourceNodeUuid, actionIndex } = event.detail;
927
+ // Don't accept drops from the same node
928
+ if (sourceNodeUuid === this.node.uuid)
929
+ return;
930
+ // Get the drop index from our tracking state
931
+ const dropIndex = (_e = (_c = (_b = this.externalDragInfo) === null || _b === void 0 ? void 0 : _b.dropIndex) !== null && _c !== void 0 ? _c : (_d = this.node.actions) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0;
932
+ // Clear external drag state
933
+ this.externalDragInfo = null;
934
+ // Remove the action from the source node
935
+ const store = getStore();
936
+ if (!store)
937
+ return;
938
+ const flowDefinition = store.getState().flowDefinition;
939
+ if (!flowDefinition)
940
+ return;
941
+ const sourceNode = flowDefinition.nodes.find((n) => n.uuid === sourceNodeUuid);
942
+ if (sourceNode) {
943
+ const updatedSourceActions = sourceNode.actions.filter((_a, idx) => idx !== actionIndex);
944
+ // If source node has no actions left, remove it
945
+ if (updatedSourceActions.length === 0) {
946
+ this.fireCustomEvent(CustomEventType.NodeDeleted, {
947
+ uuid: sourceNodeUuid
948
+ });
949
+ }
950
+ else {
951
+ // Update source node
952
+ const updatedSourceNode = {
953
+ ...sourceNode,
954
+ actions: updatedSourceActions
955
+ };
956
+ (_f = getStore()) === null || _f === void 0 ? void 0 : _f.getState().updateNode(sourceNodeUuid, updatedSourceNode);
957
+ }
958
+ }
959
+ // Add the action to this node at the calculated position
960
+ const newActions = [...this.node.actions];
961
+ newActions.splice(dropIndex, 0, action);
962
+ const updatedNode = { ...this.node, actions: newActions };
963
+ (_g = getStore()) === null || _g === void 0 ? void 0 : _g.getState().updateNode(this.node.uuid, updatedNode);
964
+ // Request update
965
+ this.requestUpdate();
966
+ }
693
967
  renderTitle(config, action, index, isRemoving = false) {
694
- var _a, _b;
695
- return html `<div class="cn-title" style="background:${config.color}">
696
- ${((_b = (_a = this.node) === null || _a === void 0 ? void 0 : _a.actions) === null || _b === void 0 ? void 0 : _b.length) > 1
968
+ var _b, _c, _d;
969
+ const color = config.group
970
+ ? (_b = ACTION_GROUP_METADATA[config.group]) === null || _b === void 0 ? void 0 : _b.color
971
+ : '#aaaaaa';
972
+ return html `<div class="cn-title" style="background:${color}">
973
+ ${!this.isTranslating && ((_d = (_c = this.node) === null || _c === void 0 ? void 0 : _c.actions) === null || _d === void 0 ? void 0 : _d.length) > 1
697
974
  ? html `<temba-icon class="drag-handle" name="sort"></temba-icon>`
698
- : null}
975
+ : html `<div class="title-spacer"></div>`}
699
976
 
700
977
  <div class="name">${isRemoving ? 'Remove?' : config.name}</div>
701
- <div
702
- class="remove-button"
703
- @click=${(e) => this.handleActionRemoveClick(e, action, index)}
704
- title="Remove action"
705
- >
706
-
707
- </div>
978
+ ${!this.isTranslating
979
+ ? html `<div
980
+ class="remove-button"
981
+ @click=${(e) => this.handleActionRemoveClick(e, action, index)}
982
+ title="Remove action"
983
+ >
984
+
985
+ </div>`
986
+ : html `<div class="title-spacer"></div>`}
708
987
  </div>`;
709
988
  }
710
- renderNodeTitle(config, isRemoving = false) {
989
+ renderNodeTitle(config, node, ui, isRemoving = false) {
990
+ var _b, _c;
991
+ // Get color from the appropriate metadata (either ACTION or SPLIT)
992
+ const color = config.group
993
+ ? ((_b = ACTION_GROUP_METADATA[config.group]) === null || _b === void 0 ? void 0 : _b.color) ||
994
+ ((_c = SPLIT_GROUP_METADATA[config.group]) === null || _c === void 0 ? void 0 : _c.color)
995
+ : '#aaaaaa';
711
996
  return html `<div
712
997
  class="cn-title ${isRemoving ? 'removing' : ''}"
713
- style="background:${config.color}"
998
+ style="background:${color}"
714
999
  >
715
1000
  <div class="title-spacer"></div>
716
- <div class="name">${isRemoving ? 'Remove?' : config.name}</div>
717
- <div
718
- class="remove-button"
719
- @click=${(e) => this.handleNodeRemoveClick(e)}
720
- title="Remove node"
721
- >
722
-
1001
+ <div class="name">
1002
+ ${isRemoving
1003
+ ? 'Remove?'
1004
+ : config.renderTitle
1005
+ ? config.renderTitle(node, ui)
1006
+ : html `${config.name}`}
723
1007
  </div>
1008
+ ${!this.isTranslating
1009
+ ? html `<div
1010
+ class="remove-button"
1011
+ @click=${(e) => this.handleNodeRemoveClick(e)}
1012
+ title="Remove node"
1013
+ >
1014
+
1015
+ </div>`
1016
+ : html `<div class="title-spacer"></div>`}
724
1017
  </div>`;
725
1018
  }
1019
+ renderDropPlaceholder() {
1020
+ var _b;
1021
+ const height = ((_b = this.externalDragInfo) === null || _b === void 0 ? void 0 : _b.actionHeight) || 60;
1022
+ return html `<div
1023
+ class="action sortable drop-placeholder"
1024
+ style="height: ${height}px; background: #f3f4f6; border: 2px dashed #d1d5db; border-radius: var(--curvature);"
1025
+ ></div>`;
1026
+ }
1027
+ /**
1028
+ * Get the localized version of an action if translating, otherwise return the original action.
1029
+ * Falls back to base language values if no localization exists for a field.
1030
+ */
1031
+ getLocalizedAction(action) {
1032
+ var _b, _c, _d;
1033
+ // If not translating or no flow definition, return original action
1034
+ if (!this.isTranslating ||
1035
+ !this.flowDefinition ||
1036
+ !this.languageCode ||
1037
+ this.languageCode === this.flowDefinition.language) {
1038
+ return action;
1039
+ }
1040
+ // Check if there's localization for this action
1041
+ const localization = (_d = (_c = (_b = this.flowDefinition) === null || _b === void 0 ? void 0 : _b.localization) === null || _c === void 0 ? void 0 : _c[this.languageCode]) === null || _d === void 0 ? void 0 : _d[action.uuid];
1042
+ if (!localization) {
1043
+ // No localization available, return original action
1044
+ return action;
1045
+ }
1046
+ // Create a new action with localized values, falling back to base language
1047
+ const localizedAction = { ...action };
1048
+ // Apply localized values for each field
1049
+ Object.keys(localization).forEach((field) => {
1050
+ const localizedValue = localization[field];
1051
+ if (Array.isArray(localizedValue)) {
1052
+ // Localized values are stored as arrays
1053
+ if (localizedValue.length > 0) {
1054
+ // For single-value fields like 'text', take the first element
1055
+ // For array fields like 'quick_replies', use the whole array
1056
+ if (Array.isArray(action[field])) {
1057
+ localizedAction[field] = localizedValue;
1058
+ }
1059
+ else {
1060
+ localizedAction[field] = localizedValue[0];
1061
+ }
1062
+ }
1063
+ }
1064
+ });
1065
+ return localizedAction;
1066
+ }
726
1067
  renderAction(node, action, index) {
1068
+ var _b, _c, _d;
727
1069
  const config = ACTION_CONFIG[action.type];
728
1070
  const isRemoving = this.actionRemovingState.has(action.uuid);
1071
+ const isLocalizable = (config === null || config === void 0 ? void 0 : config.localizable) && config.localizable.length > 0;
1072
+ const isDisabled = this.isTranslating && !isLocalizable;
1073
+ // Check if this action has localization data
1074
+ const hasLocalization = this.isTranslating &&
1075
+ ((_d = (_c = (_b = this.flowDefinition) === null || _b === void 0 ? void 0 : _b.localization) === null || _c === void 0 ? void 0 : _c[this.languageCode]) === null || _d === void 0 ? void 0 : _d[action.uuid]);
1076
+ // Get the localized action if translating
1077
+ const displayAction = this.getLocalizedAction(action);
729
1078
  if (config) {
730
- return html `<div
731
- class="action sortable ${action.type} ${isRemoving ? 'removing' : ''}"
732
- id="action-${index}"
733
- >
1079
+ const classes = [
1080
+ 'action',
1081
+ 'sortable',
1082
+ action.type,
1083
+ isRemoving ? 'removing' : '',
1084
+ isLocalizable && this.isTranslating ? 'localizable' : '',
1085
+ hasLocalization ? 'has-localization' : '',
1086
+ isDisabled ? 'non-localizable' : ''
1087
+ ]
1088
+ .filter(Boolean)
1089
+ .join(' ');
1090
+ return html `<div class="${classes}" id="action-${index}">
734
1091
  <div
735
1092
  class="action-content"
736
- @mousedown=${(e) => this.handleActionMouseDown(e, action)}
737
- @mouseup=${(e) => this.handleActionMouseUp(e, action)}
738
- style="cursor: pointer; background: #fff"
1093
+ @mousedown=${(e) => !isDisabled && this.handleActionMouseDown(e, action)}
1094
+ @mouseup=${(e) => !isDisabled && this.handleActionMouseUp(e, action)}
1095
+ style="cursor: ${isDisabled ? 'not-allowed' : 'pointer'}"
739
1096
  >
740
1097
  ${this.renderTitle(config, action, index, isRemoving)}
741
1098
  <div class="body">
742
1099
  ${config.render
743
- ? config.render(node, action)
1100
+ ? config.render(node, displayAction)
744
1101
  : html `<pre>${action.type}</pre>`}
745
1102
  </div>
746
1103
  </div>
@@ -760,6 +1117,25 @@ export class CanvasNode extends RapidElement {
760
1117
  ${action.type}
761
1118
  </div>`;
762
1119
  }
1120
+ renderActionsWithPlaceholder() {
1121
+ if (!this.externalDragInfo) {
1122
+ // No external drag, render normally
1123
+ return this.node.actions.map((action, index) => this.renderAction(this.node, action, index));
1124
+ }
1125
+ // Insert placeholder at the drop index
1126
+ const result = [];
1127
+ for (let i = 0; i < this.node.actions.length; i++) {
1128
+ if (i === this.externalDragInfo.dropIndex) {
1129
+ result.push(this.renderDropPlaceholder());
1130
+ }
1131
+ result.push(this.renderAction(this.node, this.node.actions[i], i));
1132
+ }
1133
+ // If dropping at the end, add placeholder after all actions
1134
+ if (this.externalDragInfo.dropIndex >= this.node.actions.length) {
1135
+ result.push(this.renderDropPlaceholder());
1136
+ }
1137
+ return result;
1138
+ }
763
1139
  renderRouter(router, ui) {
764
1140
  const nodeConfig = NODE_CONFIG[ui.type];
765
1141
  if (nodeConfig) {
@@ -779,19 +1155,48 @@ export class CanvasNode extends RapidElement {
779
1155
  }
780
1156
  }
781
1157
  renderCategories(node) {
1158
+ var _b;
782
1159
  if (!node.router || !node.router.categories) {
783
1160
  return null;
784
1161
  }
1162
+ // Check if this node type supports category localization
1163
+ const nodeConfig = NODE_CONFIG[(_b = this.ui) === null || _b === void 0 ? void 0 : _b.type];
1164
+ const supportsLocalization = (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.localizable) === 'categories';
785
1165
  return html `<div class="categories">
786
1166
  ${repeat(node.router.categories, (category) => category.uuid, (category) => {
1167
+ var _b, _c;
787
1168
  const exit = node.exits.find((exit) => exit.uuid == category.exit_uuid);
1169
+ // Get localized category name if translating
1170
+ let displayName = category.name;
1171
+ let isLocalized = false;
1172
+ if (this.isTranslating &&
1173
+ this.languageCode !== 'eng' &&
1174
+ supportsLocalization) {
1175
+ const localization = (_c = (_b = this.flowDefinition) === null || _b === void 0 ? void 0 : _b.localization) === null || _c === void 0 ? void 0 : _c[this.languageCode];
1176
+ if (localization && localization[category.uuid]) {
1177
+ const categoryLocalization = localization[category.uuid];
1178
+ if (categoryLocalization.name && categoryLocalization.name[0]) {
1179
+ displayName = categoryLocalization.name[0];
1180
+ isLocalized = true;
1181
+ }
1182
+ }
1183
+ }
1184
+ // Category is localizable if: translating, supports localization, categories enabled, and not base language
1185
+ const isLocalizable = this.isTranslating &&
1186
+ this.languageCode !== 'eng' &&
1187
+ supportsLocalization &&
1188
+ this.includeCategoriesInTranslation &&
1189
+ !isLocalized;
788
1190
  return html `<div
789
- class="category"
1191
+ class=${getClasses({
1192
+ category: true,
1193
+ localizable: isLocalizable
1194
+ })}
790
1195
  @mousedown=${(e) => this.handleNodeMouseDown(e)}
791
1196
  @mouseup=${(e) => this.handleNodeMouseUp(e)}
792
1197
  style="cursor: pointer;"
793
1198
  >
794
- <div class="cn-title">${category.name}</div>
1199
+ <div class="cn-title">${displayName}</div>
795
1200
  ${this.renderExit(exit)}
796
1201
  </div>`;
797
1202
  })}
@@ -815,12 +1220,19 @@ export class CanvasNode extends RapidElement {
815
1220
  return html `<div class="node">Loading...</div>`;
816
1221
  }
817
1222
  const nodeConfig = NODE_CONFIG[this.ui.type];
1223
+ // Check if this node should be disabled (grayed out)
1224
+ const supportsLocalization = (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.localizable) === 'categories';
1225
+ const isNodeDisabled = this.isTranslating &&
1226
+ supportsLocalization &&
1227
+ !this.includeCategoriesInTranslation;
818
1228
  return html `
819
1229
  <div
820
1230
  id="${this.node.uuid}"
821
- class="node ${this.ui.type === 'execute_actions'
822
- ? 'execute-actions'
823
- : ''}"
1231
+ class=${getClasses({
1232
+ node: true,
1233
+ 'execute-actions': this.ui.type === 'execute_actions',
1234
+ 'non-localizable': isNodeDisabled
1235
+ })}
824
1236
  style="left:${this.ui.position.left}px;top:${this.ui.position.top}px"
825
1237
  >
826
1238
  ${nodeConfig && nodeConfig.type !== 'execute_actions'
@@ -830,26 +1242,44 @@ export class CanvasNode extends RapidElement {
830
1242
  @mouseup=${(e) => this.handleNodeMouseUp(e)}
831
1243
  style="cursor: pointer;"
832
1244
  >
833
- ${this.renderNodeTitle(nodeConfig, this.actionRemovingState.has(this.node.uuid))}
834
- ${nodeConfig.render ? nodeConfig.render(this.node) : null}
1245
+ ${this.renderNodeTitle(nodeConfig, this.node, this.ui, this.actionRemovingState.has(this.node.uuid))}
1246
+ ${nodeConfig.render
1247
+ ? nodeConfig.render(this.node, this.ui)
1248
+ : null}
835
1249
  </div>
836
1250
  </div>`
837
1251
  : this.node.actions.length > 0
838
1252
  ? this.ui.type === 'execute_actions'
839
1253
  ? html `<temba-sortable-list
840
1254
  dragHandle="drag-handle"
1255
+ externalDrag
841
1256
  @temba-order-changed="${this.handleActionOrderChanged}"
1257
+ @temba-drag-start="${this.handleActionDragStart}"
1258
+ @temba-drag-external="${this.handleActionDragExternal}"
1259
+ @temba-drag-internal="${this.handleActionDragInternal}"
1260
+ @temba-drag-stop="${this.handleActionDragStop}"
842
1261
  >
843
- ${this.node.actions.map((action, index) => this.renderAction(this.node, action, index))}
1262
+ ${this.renderActionsWithPlaceholder()}
844
1263
  </temba-sortable-list>`
845
1264
  : html `${this.node.actions.map((action, index) => this.renderAction(this.node, action, index))}`
846
1265
  : ''}
847
1266
  ${this.node.router
848
- ? html ` ${this.renderRouter(this.node.router, this.ui)}
849
- ${this.renderCategories(this.node)}`
1267
+ ? html `<div class="router-section">
1268
+ ${this.renderRouter(this.node.router, this.ui)}
1269
+ ${this.renderCategories(this.node)}
1270
+ </div>`
850
1271
  : html `<div class="action-exits">
851
1272
  ${repeat(this.node.exits, (exit) => exit.uuid, (exit) => this.renderExit(exit))}
852
1273
  </div>`}
1274
+ ${this.ui.type === 'execute_actions' && !this.isTranslating
1275
+ ? html `<div
1276
+ class="add-action-button"
1277
+ @click=${(e) => this.handleAddActionClick(e)}
1278
+ title="Add action"
1279
+ >
1280
+ <temba-icon name="add"></temba-icon>
1281
+ </div>`
1282
+ : ''}
853
1283
  </div>
854
1284
  `;
855
1285
  }
@@ -863,4 +1293,16 @@ __decorate([
863
1293
  __decorate([
864
1294
  property({ type: Object })
865
1295
  ], CanvasNode.prototype, "ui", void 0);
1296
+ __decorate([
1297
+ fromStore(zustand, (state) => state.isTranslating)
1298
+ ], CanvasNode.prototype, "isTranslating", void 0);
1299
+ __decorate([
1300
+ fromStore(zustand, (state) => state.languageCode)
1301
+ ], CanvasNode.prototype, "languageCode", void 0);
1302
+ __decorate([
1303
+ fromStore(zustand, (state) => state.flowDefinition)
1304
+ ], CanvasNode.prototype, "flowDefinition", void 0);
1305
+ __decorate([
1306
+ fromStore(zustand, (state) => { var _b, _c, _d; return ((_d = (_c = (_b = state.flowDefinition) === null || _b === void 0 ? void 0 : _b._ui) === null || _c === void 0 ? void 0 : _c.translation_filters) === null || _d === void 0 ? void 0 : _d.categories) || false; })
1307
+ ], CanvasNode.prototype, "includeCategoriesInTranslation", void 0);
866
1308
  //# sourceMappingURL=CanvasNode.js.map