@nyaruka/temba-components 0.129.3 → 0.129.5

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 (304) hide show
  1. package/.eslintrc.js +1 -0
  2. package/.github/workflows/build.yml +135 -3
  3. package/CHANGELOG.md +19 -0
  4. package/demo/data/flows/sample-flow.json +110 -87
  5. package/demo/field-config-demo.html +135 -0
  6. package/dist/temba-components.js +1257 -675
  7. package/dist/temba-components.js.map +1 -1
  8. package/docs/ActionEditor-Migration.md +118 -0
  9. package/out-tsc/src/events.js.map +1 -1
  10. package/out-tsc/src/flow/{EditorNode.js → CanvasNode.js} +345 -42
  11. package/out-tsc/src/flow/CanvasNode.js.map +1 -0
  12. package/out-tsc/src/flow/Editor.js +107 -3
  13. package/out-tsc/src/flow/Editor.js.map +1 -1
  14. package/out-tsc/src/flow/NodeEditor.js +1211 -0
  15. package/out-tsc/src/flow/NodeEditor.js.map +1 -0
  16. package/out-tsc/src/flow/Plumber.js +0 -6
  17. package/out-tsc/src/flow/Plumber.js.map +1 -1
  18. package/out-tsc/src/flow/actions/add_contact_groups.js +40 -0
  19. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -0
  20. package/out-tsc/src/flow/actions/add_contact_urn.js +16 -0
  21. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -0
  22. package/out-tsc/src/flow/actions/add_input_labels.js +11 -0
  23. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -0
  24. package/out-tsc/src/flow/actions/call_classifier.js +11 -0
  25. package/out-tsc/src/flow/actions/call_classifier.js.map +1 -0
  26. package/out-tsc/src/flow/actions/call_llm.js +11 -0
  27. package/out-tsc/src/flow/actions/call_llm.js.map +1 -0
  28. package/out-tsc/src/flow/actions/call_resthook.js +11 -0
  29. package/out-tsc/src/flow/actions/call_resthook.js.map +1 -0
  30. package/out-tsc/src/flow/actions/call_webhook.js +122 -0
  31. package/out-tsc/src/flow/actions/call_webhook.js.map +1 -0
  32. package/out-tsc/src/flow/actions/enter_flow.js +14 -0
  33. package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
  34. package/out-tsc/src/flow/actions/open_ticket.js +11 -0
  35. package/out-tsc/src/flow/actions/open_ticket.js.map +1 -0
  36. package/out-tsc/src/flow/actions/play_audio.js +11 -0
  37. package/out-tsc/src/flow/actions/play_audio.js.map +1 -0
  38. package/out-tsc/src/flow/actions/remove_contact_groups.js +62 -0
  39. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -0
  40. package/out-tsc/src/flow/actions/request_optin.js +11 -0
  41. package/out-tsc/src/flow/actions/request_optin.js.map +1 -0
  42. package/out-tsc/src/flow/actions/say_msg.js +11 -0
  43. package/out-tsc/src/flow/actions/say_msg.js.map +1 -0
  44. package/out-tsc/src/flow/actions/send_broadcast.js +33 -0
  45. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -0
  46. package/out-tsc/src/flow/actions/send_email.js +56 -0
  47. package/out-tsc/src/flow/actions/send_email.js.map +1 -0
  48. package/out-tsc/src/flow/actions/send_msg.js +55 -0
  49. package/out-tsc/src/flow/actions/send_msg.js.map +1 -0
  50. package/out-tsc/src/flow/actions/set_contact_channel.js +12 -0
  51. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -0
  52. package/out-tsc/src/flow/actions/set_contact_field.js +12 -0
  53. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -0
  54. package/out-tsc/src/flow/actions/set_contact_language.js +10 -0
  55. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -0
  56. package/out-tsc/src/flow/actions/set_contact_name.js +10 -0
  57. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -0
  58. package/out-tsc/src/flow/actions/set_contact_status.js +10 -0
  59. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -0
  60. package/out-tsc/src/flow/actions/set_run_result.js +10 -0
  61. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -0
  62. package/out-tsc/src/flow/actions/split_by_expression_example.js +77 -0
  63. package/out-tsc/src/flow/actions/split_by_expression_example.js.map +1 -0
  64. package/out-tsc/src/flow/actions/start_session.js +11 -0
  65. package/out-tsc/src/flow/actions/start_session.js.map +1 -0
  66. package/out-tsc/src/flow/actions/transfer_airtime.js +11 -0
  67. package/out-tsc/src/flow/actions/transfer_airtime.js.map +1 -0
  68. package/out-tsc/src/flow/config.js +88 -193
  69. package/out-tsc/src/flow/config.js.map +1 -1
  70. package/out-tsc/src/flow/nodes/execute_actions.js +4 -0
  71. package/out-tsc/src/flow/nodes/execute_actions.js.map +1 -0
  72. package/out-tsc/src/flow/nodes/split_by_airtime.js +9 -0
  73. package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -0
  74. package/out-tsc/src/flow/nodes/split_by_contact_field.js +7 -0
  75. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -0
  76. package/out-tsc/src/flow/nodes/split_by_expression.js +7 -0
  77. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -0
  78. package/out-tsc/src/flow/nodes/split_by_groups.js +7 -0
  79. package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -0
  80. package/out-tsc/src/flow/nodes/split_by_random.js +10 -0
  81. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -0
  82. package/out-tsc/src/flow/nodes/split_by_run_result.js +7 -0
  83. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -0
  84. package/out-tsc/src/flow/nodes/split_by_scheme.js +7 -0
  85. package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -0
  86. package/out-tsc/src/flow/nodes/split_by_subflow.js +9 -0
  87. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -0
  88. package/out-tsc/src/flow/nodes/split_by_webhook.js +18 -0
  89. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -0
  90. package/out-tsc/src/flow/nodes/wait_for_audio.js +7 -0
  91. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
  92. package/out-tsc/src/flow/nodes/wait_for_digits.js +7 -0
  93. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -0
  94. package/out-tsc/src/flow/nodes/wait_for_image.js +7 -0
  95. package/out-tsc/src/flow/nodes/wait_for_image.js.map +1 -0
  96. package/out-tsc/src/flow/nodes/wait_for_location.js +7 -0
  97. package/out-tsc/src/flow/nodes/wait_for_location.js.map +1 -0
  98. package/out-tsc/src/flow/nodes/wait_for_menu.js +7 -0
  99. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -0
  100. package/out-tsc/src/flow/nodes/wait_for_response.js +7 -0
  101. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -0
  102. package/out-tsc/src/flow/nodes/wait_for_video.js +7 -0
  103. package/out-tsc/src/flow/nodes/wait_for_video.js.map +1 -0
  104. package/out-tsc/src/flow/types.js +79 -0
  105. package/out-tsc/src/flow/types.js.map +1 -0
  106. package/out-tsc/src/flow/utils.js +65 -0
  107. package/out-tsc/src/flow/utils.js.map +1 -0
  108. package/out-tsc/src/form/ArrayEditor.js +199 -0
  109. package/out-tsc/src/form/ArrayEditor.js.map +1 -0
  110. package/out-tsc/src/form/BaseListEditor.js +128 -0
  111. package/out-tsc/src/form/BaseListEditor.js.map +1 -0
  112. package/out-tsc/src/form/Checkbox.js +17 -2
  113. package/out-tsc/src/form/Checkbox.js.map +1 -1
  114. package/out-tsc/src/form/Completion.js +6 -0
  115. package/out-tsc/src/form/Completion.js.map +1 -1
  116. package/out-tsc/src/form/FormField.js +110 -11
  117. package/out-tsc/src/form/FormField.js.map +1 -1
  118. package/out-tsc/src/form/KeyValueEditor.js +223 -0
  119. package/out-tsc/src/form/KeyValueEditor.js.map +1 -0
  120. package/out-tsc/src/form/select/Select.js +92 -32
  121. package/out-tsc/src/form/select/Select.js.map +1 -1
  122. package/out-tsc/src/interfaces.js +6 -0
  123. package/out-tsc/src/interfaces.js.map +1 -1
  124. package/out-tsc/src/live/ContactChat.js +2 -76
  125. package/out-tsc/src/live/ContactChat.js.map +1 -1
  126. package/out-tsc/temba-modules.js +9 -2
  127. package/out-tsc/temba-modules.js.map +1 -1
  128. package/out-tsc/test/ActionHelper.js +116 -0
  129. package/out-tsc/test/ActionHelper.js.map +1 -0
  130. package/out-tsc/test/actions/add_contact_groups.test.js +66 -0
  131. package/out-tsc/test/actions/add_contact_groups.test.js.map +1 -0
  132. package/out-tsc/test/actions/remove_contact_groups.test.js +226 -0
  133. package/out-tsc/test/actions/remove_contact_groups.test.js.map +1 -0
  134. package/out-tsc/test/actions/send_email.test.js +160 -0
  135. package/out-tsc/test/actions/send_email.test.js.map +1 -0
  136. package/out-tsc/test/actions/send_msg.test.js +95 -0
  137. package/out-tsc/test/actions/send_msg.test.js.map +1 -0
  138. package/out-tsc/test/temba-action-editing-integration.test.js +183 -0
  139. package/out-tsc/test/temba-action-editing-integration.test.js.map +1 -0
  140. package/out-tsc/test/temba-checkbox.test.js +1 -1
  141. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  142. package/out-tsc/test/temba-field-config.test.js +133 -0
  143. package/out-tsc/test/temba-field-config.test.js.map +1 -0
  144. package/out-tsc/test/temba-flow-editor-node.test.js +14 -14
  145. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  146. package/out-tsc/test/temba-node-editor.test.js +283 -0
  147. package/out-tsc/test/temba-node-editor.test.js.map +1 -0
  148. package/out-tsc/test/temba-select.test.js +158 -0
  149. package/out-tsc/test/temba-select.test.js.map +1 -1
  150. package/package.json +1 -1
  151. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  152. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  153. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  154. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  155. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  156. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  157. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  158. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  159. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  160. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  161. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  162. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  163. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  164. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  165. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  166. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  167. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  168. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  169. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  170. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  171. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  172. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  173. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  174. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  175. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  176. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  177. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  178. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  179. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  180. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  181. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  182. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  183. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  184. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  185. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  186. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  187. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  188. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  189. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  190. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  191. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  192. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  193. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  194. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  195. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  196. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  197. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  198. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  199. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  200. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  201. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  202. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  203. package/screenshots/truth/editor/router.png +0 -0
  204. package/screenshots/truth/editor/send_msg.png +0 -0
  205. package/screenshots/truth/editor/set_contact_language.png +0 -0
  206. package/screenshots/truth/editor/set_contact_name.png +0 -0
  207. package/screenshots/truth/editor/set_run_result.png +0 -0
  208. package/screenshots/truth/editor/wait.png +0 -0
  209. package/screenshots/truth/formfield/markdown-errors.png +0 -0
  210. package/screenshots/truth/formfield/plain-text-errors.png +0 -0
  211. package/screenshots/truth/formfield/widget-only-markdown-errors.png +0 -0
  212. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
  213. package/src/events.ts +1 -40
  214. package/src/flow/{EditorNode.ts → CanvasNode.ts} +424 -48
  215. package/src/flow/Editor.ts +140 -4
  216. package/src/flow/NodeEditor.ts +1454 -0
  217. package/src/flow/Plumber.ts +0 -9
  218. package/src/flow/actions/add_contact_groups.ts +42 -0
  219. package/src/flow/actions/add_contact_urn.ts +17 -0
  220. package/src/flow/actions/add_input_labels.ts +12 -0
  221. package/src/flow/actions/call_classifier.ts +12 -0
  222. package/src/flow/actions/call_llm.ts +12 -0
  223. package/src/flow/actions/call_resthook.ts +12 -0
  224. package/src/flow/actions/call_webhook.ts +133 -0
  225. package/src/flow/actions/enter_flow.ts +15 -0
  226. package/src/flow/actions/open_ticket.ts +12 -0
  227. package/src/flow/actions/play_audio.ts +12 -0
  228. package/src/flow/actions/remove_contact_groups.ts +66 -0
  229. package/src/flow/actions/request_optin.ts +12 -0
  230. package/src/flow/actions/say_msg.ts +12 -0
  231. package/src/flow/actions/send_broadcast.ts +35 -0
  232. package/src/flow/actions/send_email.ts +60 -0
  233. package/src/flow/actions/send_msg.ts +58 -0
  234. package/src/flow/actions/set_contact_channel.ts +13 -0
  235. package/src/flow/actions/set_contact_field.ts +13 -0
  236. package/src/flow/actions/set_contact_language.ts +11 -0
  237. package/src/flow/actions/set_contact_name.ts +11 -0
  238. package/src/flow/actions/set_contact_status.ts +11 -0
  239. package/src/flow/actions/set_run_result.ts +11 -0
  240. package/src/flow/actions/split_by_expression_example.ts +88 -0
  241. package/src/flow/actions/start_session.ts +12 -0
  242. package/src/flow/actions/transfer_airtime.ts +12 -0
  243. package/src/flow/config.ts +93 -232
  244. package/src/flow/nodes/execute_actions.ts +5 -0
  245. package/src/flow/nodes/split_by_airtime.ts +9 -0
  246. package/src/flow/nodes/split_by_contact_field.ts +7 -0
  247. package/src/flow/nodes/split_by_expression.ts +7 -0
  248. package/src/flow/nodes/split_by_groups.ts +7 -0
  249. package/src/flow/nodes/split_by_random.ts +10 -0
  250. package/src/flow/nodes/split_by_run_result.ts +7 -0
  251. package/src/flow/nodes/split_by_scheme.ts +7 -0
  252. package/src/flow/nodes/split_by_subflow.ts +9 -0
  253. package/src/flow/nodes/split_by_webhook.ts +19 -0
  254. package/src/flow/nodes/wait_for_audio.ts +7 -0
  255. package/src/flow/nodes/wait_for_digits.ts +7 -0
  256. package/src/flow/nodes/wait_for_image.ts +7 -0
  257. package/src/flow/nodes/wait_for_location.ts +7 -0
  258. package/src/flow/nodes/wait_for_menu.ts +7 -0
  259. package/src/flow/nodes/wait_for_response.ts +7 -0
  260. package/src/flow/nodes/wait_for_video.ts +7 -0
  261. package/src/flow/types.ts +352 -0
  262. package/src/flow/utils.ts +76 -0
  263. package/src/form/ArrayEditor.ts +240 -0
  264. package/src/form/BaseListEditor.ts +177 -0
  265. package/src/form/Checkbox.ts +22 -3
  266. package/src/form/Completion.ts +6 -0
  267. package/src/form/FormField.ts +115 -11
  268. package/src/form/KeyValueEditor.ts +251 -0
  269. package/src/form/select/Select.ts +105 -32
  270. package/src/interfaces.ts +7 -2
  271. package/src/live/ContactChat.ts +3 -97
  272. package/src/store/flow-definition.d.ts +6 -1
  273. package/static/api/contacts.json +30 -0
  274. package/static/api/groups.json +4 -426
  275. package/static/api/locations.json +24 -0
  276. package/static/api/media.json +5 -0
  277. package/static/api/optins.json +16 -0
  278. package/static/api/orgs.json +13 -0
  279. package/static/api/topics.json +21 -0
  280. package/static/api/users.json +26 -0
  281. package/static/css/temba-components.css +3 -6
  282. package/temba-modules.ts +9 -2
  283. package/test/ActionHelper.ts +142 -0
  284. package/test/actions/add_contact_groups.test.ts +89 -0
  285. package/test/actions/remove_contact_groups.test.ts +265 -0
  286. package/test/actions/send_email.test.ts +214 -0
  287. package/test/actions/send_msg.test.ts +130 -0
  288. package/test/temba-action-editing-integration.test.ts +240 -0
  289. package/test/temba-checkbox.test.ts +1 -1
  290. package/test/temba-field-config.test.ts +152 -0
  291. package/test/temba-flow-editor-node.test.ts +18 -18
  292. package/test/temba-node-editor.test.ts +353 -0
  293. package/test/temba-select.test.ts +234 -0
  294. package/test-assets/contacts/history.json +11 -33
  295. package/web-dev-server.config.mjs +34 -0
  296. package/.github/workflows/coverage.yml +0 -80
  297. package/demo/sticky-note-demo.html +0 -155
  298. package/out-tsc/src/flow/EditorNode.js.map +0 -1
  299. package/out-tsc/src/flow/render.js +0 -358
  300. package/out-tsc/src/flow/render.js.map +0 -1
  301. package/out-tsc/test/temba-flow-render.test.js +0 -794
  302. package/out-tsc/test/temba-flow-render.test.js.map +0 -1
  303. package/src/flow/render.ts +0 -443
  304. package/test/temba-flow-render.test.ts +0 -1003
@@ -0,0 +1,352 @@
1
+ import { TemplateResult } from 'lit-html';
2
+ import { Action } from '../store/flow-definition';
3
+
4
+ export interface ValidationResult {
5
+ valid: boolean;
6
+ errors: { [key: string]: string };
7
+ }
8
+
9
+ // Component attribute interfaces - these define what's allowed for each component type
10
+ export interface TextInputAttributes {
11
+ type?: 'text' | 'email' | 'number' | 'url' | 'tel';
12
+ placeholder?: string;
13
+ clearable?: boolean;
14
+ maxlength?: number;
15
+ gsm?: boolean;
16
+ autogrow?: boolean;
17
+ textarea?: boolean;
18
+ submitOnEnter?: boolean;
19
+ }
20
+
21
+ export interface CompletionAttributes {
22
+ placeholder?: string;
23
+ clearable?: boolean;
24
+ maxlength?: number;
25
+ gsm?: boolean;
26
+ autogrow?: boolean;
27
+ textarea?: boolean;
28
+ expressions?: string;
29
+ counter?: string;
30
+ minHeight?: number;
31
+ }
32
+
33
+ export interface SelectAttributes {
34
+ placeholder?: string;
35
+ multi?: boolean;
36
+ searchable?: boolean;
37
+ tags?: boolean;
38
+ emails?: boolean;
39
+ clearable?: boolean;
40
+ endpoint?: string;
41
+ valueKey?: string;
42
+ nameKey?: string;
43
+ queryParam?: string;
44
+ maxItems?: number;
45
+ maxItemsText?: string;
46
+ expressions?: string;
47
+ options?: Array<{ name: string; value: any }>;
48
+ sorted?: boolean;
49
+ allowCreate?: boolean;
50
+ jsonValue?: boolean;
51
+ spaceSelect?: boolean;
52
+ infoText?: string;
53
+ }
54
+
55
+ export interface CheckboxAttributes {
56
+ label?: string;
57
+ size?: number;
58
+ disabled?: boolean;
59
+ animateChange?: string;
60
+ }
61
+
62
+ export interface SliderAttributes {
63
+ min?: number;
64
+ max?: number;
65
+ range?: boolean;
66
+ }
67
+
68
+ // Widget configuration using discriminated union for type safety
69
+ export type WidgetConfig =
70
+ | { type: 'temba-textinput'; attributes?: TextInputAttributes }
71
+ | { type: 'temba-completion'; attributes?: CompletionAttributes }
72
+ | { type: 'temba-select'; attributes?: SelectAttributes }
73
+ | { type: 'temba-checkbox'; attributes?: CheckboxAttributes }
74
+ | { type: 'temba-slider'; attributes?: SliderAttributes }
75
+ | { type: string; attributes?: { [key: string]: any } }; // Generic fallback
76
+
77
+ // Property configuration with the clean structure you want
78
+ export interface PropertyConfig {
79
+ // Form field metadata
80
+ label?: string;
81
+ helpText?: string;
82
+ required?: boolean;
83
+ maxLength?: number;
84
+ minLength?: number;
85
+ pattern?: string;
86
+
87
+ // Widget configuration
88
+ widget: WidgetConfig;
89
+
90
+ // Conditional behavior based on other field values
91
+ conditions?: {
92
+ // When to show this field
93
+ visible?: (formData: any) => boolean;
94
+
95
+ // When this field is disabled
96
+ disabled?: (formData: any) => boolean;
97
+ };
98
+ }
99
+
100
+ export interface NodeConfig {
101
+ type: string;
102
+ name?: string;
103
+ color?: string;
104
+ action?: ActionConfig;
105
+ router?: {
106
+ type: 'switch' | 'random';
107
+ defaultCategory?: string;
108
+ operand?: string;
109
+ configurable?: boolean; // can the rules be configured in the UI
110
+ rules?: {
111
+ type: 'has_number_between' | 'has_string' | 'has_value' | 'has_not_value';
112
+ arguments: string[];
113
+ categoryName: string;
114
+ }[];
115
+ };
116
+ properties?: { [key: string]: PropertyConfig };
117
+ toFormData?: (node: any) => any;
118
+ fromFormData?: (formData: any, originalNode: any) => any;
119
+ }
120
+
121
+ // New field configuration system for generic form generation
122
+ export interface BaseFieldConfig {
123
+ label?: string;
124
+ required?: boolean;
125
+ evaluated?: boolean; // if this field supports expression evaluation
126
+ dependsOn?: string[]; // fields this field depends on
127
+ computeValue?: (
128
+ values: Record<string, any>,
129
+ currentValue: any,
130
+ originalValues?: Record<string, any>
131
+ ) => any;
132
+
133
+ // Validation properties
134
+ minLength?: number;
135
+ maxLength?: number;
136
+ pattern?: string;
137
+ helpText?: string;
138
+
139
+ // Layout properties
140
+ maxWidth?: string; // CSS max-width value (e.g., '200px', '50%', '10rem')
141
+
142
+ // Conditional rendering
143
+ conditions?: {
144
+ visible?: (formData: Record<string, any>) => boolean;
145
+ disabled?: (formData: Record<string, any>) => boolean;
146
+ };
147
+ }
148
+
149
+ export interface TextFieldConfig extends BaseFieldConfig {
150
+ type: 'text';
151
+ placeholder?: string;
152
+ }
153
+
154
+ export interface TextareaFieldConfig extends BaseFieldConfig {
155
+ type: 'textarea';
156
+ placeholder?: string;
157
+ rows?: number;
158
+ minHeight?: number;
159
+ }
160
+
161
+ export interface SelectFieldConfig extends BaseFieldConfig {
162
+ type: 'select';
163
+ options: string[] | { value: string; label: string }[];
164
+ multi?: boolean;
165
+ searchable?: boolean;
166
+ tags?: boolean;
167
+ placeholder?: string;
168
+ maxItems?: number;
169
+ valueKey?: string;
170
+ nameKey?: string;
171
+ endpoint?: string;
172
+ emails?: boolean;
173
+ }
174
+
175
+ export interface KeyValueFieldConfig extends BaseFieldConfig {
176
+ type: 'key-value';
177
+ sortable?: boolean;
178
+ keyPlaceholder?: string;
179
+ valuePlaceholder?: string;
180
+ minRows?: number;
181
+ }
182
+
183
+ export interface ArrayFieldConfig extends BaseFieldConfig {
184
+ type: 'array';
185
+ itemConfig: Record<string, FieldConfig>;
186
+ sortable?: boolean;
187
+ minItems?: number;
188
+ maxItems?: number;
189
+ itemLabel?: string;
190
+ onItemChange?: (
191
+ itemIndex: number,
192
+ field: string,
193
+ value: any,
194
+ allItems: any[]
195
+ ) => any[];
196
+ }
197
+
198
+ export interface CheckboxFieldConfig extends BaseFieldConfig {
199
+ type: 'checkbox';
200
+ size?: number;
201
+ animateChange?: string;
202
+ }
203
+
204
+ export type FieldConfig =
205
+ | TextFieldConfig
206
+ | TextareaFieldConfig
207
+ | SelectFieldConfig
208
+ | KeyValueFieldConfig
209
+ | ArrayFieldConfig
210
+ | CheckboxFieldConfig;
211
+
212
+ // Layout configurations for better form organization
213
+ // Recursive layout system - any layout item can contain other layout items
214
+
215
+ export interface FieldItemConfig {
216
+ type: 'field';
217
+ field: string; // field name to render
218
+ }
219
+
220
+ export interface RowLayoutConfig {
221
+ type: 'row';
222
+ items: LayoutItem[]; // can contain fields, groups, or other rows
223
+ gap?: string; // CSS gap value, defaults to '1rem'
224
+ }
225
+
226
+ export interface GroupLayoutConfig {
227
+ type: 'group';
228
+ label: string;
229
+ items: LayoutItem[]; // can contain fields, rows, or other groups
230
+ collapsible?: boolean;
231
+ collapsed?: boolean; // initial state if collapsible
232
+ helpText?: string;
233
+ }
234
+
235
+ export type LayoutItem =
236
+ | FieldItemConfig
237
+ | RowLayoutConfig
238
+ | GroupLayoutConfig
239
+ | string; // string is shorthand for field
240
+
241
+ export interface ActionConfig {
242
+ name: string;
243
+ color: string;
244
+ evaluated?: string[];
245
+ render?: (node: any, action: any) => TemplateResult;
246
+
247
+ form?: Record<string, FieldConfig>;
248
+ layout?: LayoutItem[]; // optional layout configuration - array of layout items
249
+
250
+ // Action editor configuration (legacy)
251
+ // Form-level transformations
252
+ toFormData?: (action: Action) => any;
253
+ fromFormData?: (formData: any) => Action;
254
+
255
+ validate?: (action: Action) => ValidationResult;
256
+ }
257
+
258
+ export const COLORS = {
259
+ send: '#3498db',
260
+ update: '#01c1af',
261
+ broadcast: '#8e5ea7',
262
+ call: '#e68628',
263
+ create: '#df419f',
264
+ save: '#1a777c',
265
+ split: '#aaaaaa',
266
+ execute: '#666666',
267
+ wait: '#4d7dad',
268
+ add: '#309c42',
269
+ remove: '#e74c3c'
270
+ };
271
+
272
+ // Default property type mappings
273
+ export function getDefaultComponent(value: any): WidgetConfig['type'] {
274
+ if (typeof value === 'boolean') {
275
+ return 'temba-checkbox';
276
+ }
277
+ if (typeof value === 'number') {
278
+ return 'temba-textinput';
279
+ }
280
+ if (Array.isArray(value)) {
281
+ return 'temba-select'; // For arrays, use multi-select
282
+ }
283
+ // Default to text input for strings and unknown types
284
+ return 'temba-textinput';
285
+ }
286
+
287
+ // Get component properties for default mappings with proper typing
288
+ export function getDefaultComponentProps(value: any): PropertyConfig {
289
+ if (typeof value === 'boolean') {
290
+ return {
291
+ widget: { type: 'temba-checkbox' }
292
+ };
293
+ }
294
+ if (typeof value === 'number') {
295
+ return {
296
+ widget: {
297
+ type: 'temba-textinput',
298
+ attributes: { type: 'number' }
299
+ }
300
+ };
301
+ }
302
+ if (Array.isArray(value)) {
303
+ if (value.length > 0 && typeof value[0] === 'string') {
304
+ return {
305
+ widget: {
306
+ type: 'temba-select',
307
+ attributes: { multi: true, tags: true }
308
+ }
309
+ };
310
+ }
311
+ return {
312
+ widget: {
313
+ type: 'temba-select',
314
+ attributes: { multi: true }
315
+ }
316
+ };
317
+ }
318
+ return {
319
+ widget: { type: 'temba-textinput' }
320
+ };
321
+ }
322
+
323
+ // Type guard functions for working with WidgetConfig
324
+ export function isTextInputWidget(
325
+ config: WidgetConfig
326
+ ): config is { type: 'temba-textinput'; attributes?: TextInputAttributes } {
327
+ return config.type === 'temba-textinput';
328
+ }
329
+
330
+ export function isCompletionWidget(
331
+ config: WidgetConfig
332
+ ): config is { type: 'temba-completion'; attributes?: CompletionAttributes } {
333
+ return config.type === 'temba-completion';
334
+ }
335
+
336
+ export function isSelectWidget(
337
+ config: WidgetConfig
338
+ ): config is { type: 'temba-select'; attributes?: SelectAttributes } {
339
+ return config.type === 'temba-select';
340
+ }
341
+
342
+ export function isCheckboxWidget(
343
+ config: WidgetConfig
344
+ ): config is { type: 'temba-checkbox'; attributes?: CheckboxAttributes } {
345
+ return config.type === 'temba-checkbox';
346
+ }
347
+
348
+ export function isSliderWidget(
349
+ config: WidgetConfig
350
+ ): config is { type: 'slider'; attributes?: SliderAttributes } {
351
+ return config.type === 'temba-slider';
352
+ }
@@ -0,0 +1,76 @@
1
+ import { html } from 'lit-html';
2
+ import { NamedObject } from '../store/flow-definition';
3
+
4
+ /**
5
+ * Renders a single line item with optional icon
6
+ */
7
+ export const renderLineItem = (name: string, icon?: string) => {
8
+ return html`<div style="display:flex;items-align:center;">
9
+ ${icon
10
+ ? html`<temba-icon name=${icon} style="margin-right:0.5em"></temba-icon>`
11
+ : null}
12
+ <div
13
+ style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 250px;"
14
+ >
15
+ ${name}
16
+ </div>
17
+ </div>`;
18
+ };
19
+
20
+ /**
21
+ * Renders a list of named objects with optional icon, showing up to 3 items
22
+ * with a "+X more" indicator if there are more items
23
+ */
24
+ export const renderNamedObjects = (assets: NamedObject[], icon?: string) => {
25
+ return renderStringList(
26
+ assets.map((asset) => asset.name),
27
+ icon
28
+ );
29
+ };
30
+
31
+ /**
32
+ * Renders a list of strings with optional icon, showing up to 3 items
33
+ * with a "+X more" indicator if there are more items
34
+ */
35
+ export const renderStringList = (items: string[], icon?: string) => {
36
+ const itemElements = [];
37
+ const maxDisplay = 3;
38
+
39
+ // Show up to 3 items, or all 4 if exactly 4 items
40
+ const displayCount =
41
+ items.length === 4 ? 4 : Math.min(maxDisplay, items.length);
42
+
43
+ for (let i = 0; i < displayCount; i++) {
44
+ const item = items[i];
45
+ itemElements.push(renderLineItem(item, icon));
46
+ }
47
+
48
+ // Add "+X more" if there are more than 3 items (and not exactly 4)
49
+ if (items.length > maxDisplay && items.length !== 4) {
50
+ const remainingCount = items.length - maxDisplay;
51
+ itemElements.push(html`<div
52
+ style="display:flex;items-align:center;margin-top:0.2em;"
53
+ >
54
+ ${icon
55
+ ? html`<div style="margin-right:0.4em; width: 1em;"></div>` // spacing placeholder
56
+ : null}
57
+ <div style="font-size:0.8em">+${remainingCount} more</div>
58
+ </div>`);
59
+ }
60
+ return itemElements;
61
+ };
62
+
63
+ // URN scheme mapping for user-friendly display
64
+ export const urnSchemeMap: Record<string, string> = {
65
+ tel: 'Phone Number',
66
+ email: 'Email',
67
+ twitter: 'Twitter',
68
+ facebook: 'Facebook',
69
+ telegram: 'Telegram',
70
+ whatsapp: 'WhatsApp',
71
+ viber: 'Viber',
72
+ line: 'Line',
73
+ discord: 'Discord',
74
+ slack: 'Slack',
75
+ external: 'External ID'
76
+ };
@@ -0,0 +1,240 @@
1
+ import { html, css, TemplateResult } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { FieldConfig } from '../flow/types';
4
+ import { BaseListEditor, ListItem } from './BaseListEditor';
5
+
6
+ @customElement('temba-array-editor')
7
+ export class TembaArrayEditor extends BaseListEditor<ListItem> {
8
+ @property({ type: Object })
9
+ itemConfig: Record<string, FieldConfig> = {};
10
+
11
+ @property({ type: String })
12
+ itemLabel = 'Item';
13
+
14
+ @property({ type: Function })
15
+ onItemChange?: (
16
+ itemIndex: number,
17
+ field: string,
18
+ value: any,
19
+ allItems: any[]
20
+ ) => any[];
21
+
22
+ constructor() {
23
+ super();
24
+ this._items = [];
25
+ }
26
+
27
+ // External API
28
+ @property({ type: Array })
29
+ get value(): any[] {
30
+ return [...this._items];
31
+ }
32
+
33
+ set value(newValue: any[]) {
34
+ this._items = newValue || [];
35
+ this.requestUpdate();
36
+ }
37
+
38
+ // Implement abstract methods
39
+ isEmptyItem(item: ListItem): boolean {
40
+ return Object.values(item).every(
41
+ (value) => value === undefined || value === null || value === ''
42
+ );
43
+ }
44
+
45
+ createEmptyItem(): ListItem {
46
+ return {};
47
+ }
48
+
49
+ protected handleFieldChange(
50
+ itemIndex: number,
51
+ fieldName: string,
52
+ newValue: any
53
+ ) {
54
+ let updatedItems: any[];
55
+
56
+ if (this.onItemChange) {
57
+ updatedItems = this.onItemChange(
58
+ itemIndex,
59
+ fieldName,
60
+ newValue,
61
+ this._items
62
+ );
63
+ } else {
64
+ updatedItems = [...this._items];
65
+ updatedItems[itemIndex] = {
66
+ ...updatedItems[itemIndex],
67
+ [fieldName]: newValue
68
+ };
69
+ }
70
+
71
+ this.updateValue(updatedItems);
72
+ }
73
+
74
+ private computeFieldValue(
75
+ itemIndex: number,
76
+ fieldName: string,
77
+ config: FieldConfig
78
+ ): any {
79
+ const item = this._items[itemIndex] || {};
80
+ const currentValue = item[fieldName];
81
+
82
+ if (config.computeValue) {
83
+ return config.computeValue(item, currentValue);
84
+ }
85
+
86
+ return currentValue;
87
+ }
88
+
89
+ private renderField(
90
+ itemIndex: number,
91
+ fieldName: string,
92
+ config: FieldConfig
93
+ ): TemplateResult {
94
+ const computedValue = this.computeFieldValue(itemIndex, fieldName, config);
95
+
96
+ switch (config.type) {
97
+ case 'text':
98
+ return html`<temba-textinput
99
+ .value=${computedValue || ''}
100
+ .placeholder=${config.placeholder}
101
+ @change=${(e: any) =>
102
+ this.handleFieldChange(itemIndex, fieldName, e.target.value)}
103
+ ></temba-textinput>`;
104
+
105
+ case 'textarea':
106
+ return html`<temba-textinput
107
+ .value=${computedValue || ''}
108
+ .placeholder=${config.placeholder}
109
+ textarea
110
+ .rows=${config.rows || 3}
111
+ @change=${(e: any) =>
112
+ this.handleFieldChange(itemIndex, fieldName, e.target.value)}
113
+ ></temba-textinput>`;
114
+
115
+ case 'select':
116
+ return html`<temba-select
117
+ .value=${computedValue || ''}
118
+ .options=${config.options}
119
+ @change=${(e: any) =>
120
+ this.handleFieldChange(itemIndex, fieldName, e.target.value)}
121
+ ></temba-select>`;
122
+
123
+ default:
124
+ return html`<span>Unsupported field type: ${config.type}</span>`;
125
+ }
126
+ }
127
+
128
+ renderItem(item: ListItem, index: number): TemplateResult {
129
+ const canRemove = this.canRemoveItem(index);
130
+
131
+ return html`
132
+ <div class="array-item">
133
+ <div class="item-header">
134
+ <span class="item-title">${this.itemLabel} ${index + 1}</span>
135
+ ${canRemove
136
+ ? html`
137
+ <button
138
+ @click=${() => this.removeItem(index)}
139
+ class="remove-btn"
140
+ >
141
+ Remove
142
+ </button>
143
+ `
144
+ : ''}
145
+ </div>
146
+ <div class="item-fields">
147
+ ${Object.entries(this.itemConfig).map(
148
+ ([fieldName, config]) => html`
149
+ <div class="field">
150
+ <label>${config.label}${config.required ? ' *' : ''}</label>
151
+ ${this.renderField(index, fieldName, config)}
152
+ </div>
153
+ `
154
+ )}
155
+ </div>
156
+ </div>
157
+ `;
158
+ }
159
+
160
+ protected getContainerClass(): string {
161
+ return 'array-editor';
162
+ }
163
+
164
+ protected renderAddButton(): TemplateResult {
165
+ return html`
166
+ <button class="add-btn" @click=${() => this.addItem()}>
167
+ Add ${this.itemLabel}
168
+ </button>
169
+ `;
170
+ }
171
+
172
+ static styles = css`
173
+ .array-editor {
174
+ border: 1px solid #e0e0e0;
175
+ border-radius: 6px;
176
+ padding: 16px;
177
+ background: #fafafa;
178
+ }
179
+
180
+ .array-item {
181
+ border: 1px solid #d0d0d0;
182
+ border-radius: 4px;
183
+ padding: 16px;
184
+ margin-bottom: 12px;
185
+ background: white;
186
+ }
187
+
188
+ .item-header {
189
+ display: flex;
190
+ justify-content: space-between;
191
+ align-items: center;
192
+ margin-bottom: 12px;
193
+ padding-bottom: 8px;
194
+ border-bottom: 1px solid #eee;
195
+ }
196
+
197
+ .item-title {
198
+ font-weight: 600;
199
+ color: #333;
200
+ }
201
+
202
+ .item-fields {
203
+ display: grid;
204
+ gap: 12px;
205
+ }
206
+
207
+ .field label {
208
+ display: block;
209
+ margin-bottom: 4px;
210
+ font-weight: 500;
211
+ color: #555;
212
+ font-size: 14px;
213
+ }
214
+
215
+ .add-btn,
216
+ .remove-btn {
217
+ padding: 8px 16px;
218
+ border: 1px solid #ccc;
219
+ border-radius: 4px;
220
+ background: white;
221
+ cursor: pointer;
222
+ font-size: 14px;
223
+ }
224
+
225
+ .add-btn:hover,
226
+ .remove-btn:hover {
227
+ background: #f8f8f8;
228
+ }
229
+
230
+ .remove-btn {
231
+ background: #fff5f5;
232
+ border-color: #fecaca;
233
+ color: #dc2626;
234
+ }
235
+
236
+ .remove-btn:hover {
237
+ background: #fef2f2;
238
+ }
239
+ `;
240
+ }