@nyaruka/temba-components 0.129.8 → 0.129.10

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 (282) hide show
  1. package/CHANGELOG.md +37 -3
  2. package/demo/data/flows/sample-flow.json +186 -96
  3. package/demo/test-colorpicker.html +30 -0
  4. package/dist/temba-components.js +1126 -1111
  5. package/dist/temba-components.js.map +1 -1
  6. package/out-tsc/src/events.js.map +1 -1
  7. package/out-tsc/src/excellent/helpers.js +2 -2
  8. package/out-tsc/src/excellent/helpers.js.map +1 -1
  9. package/out-tsc/src/flow/CanvasNode.js +25 -7
  10. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  11. package/out-tsc/src/flow/Editor.js +11 -1
  12. package/out-tsc/src/flow/Editor.js.map +1 -1
  13. package/out-tsc/src/flow/NodeEditor.js +133 -290
  14. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  15. package/out-tsc/src/flow/actions/add_input_labels.js +40 -0
  16. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  17. package/out-tsc/src/flow/actions/call_llm.js +56 -3
  18. package/out-tsc/src/flow/actions/call_llm.js.map +1 -1
  19. package/out-tsc/src/flow/actions/call_webhook.js +1 -1
  20. package/out-tsc/src/flow/actions/call_webhook.js.map +1 -1
  21. package/out-tsc/src/flow/actions/open_ticket.js +65 -3
  22. package/out-tsc/src/flow/actions/open_ticket.js.map +1 -1
  23. package/out-tsc/src/flow/actions/set_run_result.js +75 -0
  24. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  25. package/out-tsc/src/flow/config.js +4 -0
  26. package/out-tsc/src/flow/config.js.map +1 -1
  27. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +227 -0
  28. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -0
  29. package/out-tsc/src/flow/nodes/split_by_ticket.js +18 -0
  30. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -0
  31. package/out-tsc/src/flow/nodes/wait_for_response.js +27 -1
  32. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  33. package/out-tsc/src/flow/types.js +0 -65
  34. package/out-tsc/src/flow/types.js.map +1 -1
  35. package/out-tsc/src/form/ArrayEditor.js +63 -117
  36. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  37. package/out-tsc/src/form/BaseListEditor.js +4 -3
  38. package/out-tsc/src/form/BaseListEditor.js.map +1 -1
  39. package/out-tsc/src/form/Checkbox.js +77 -24
  40. package/out-tsc/src/form/Checkbox.js.map +1 -1
  41. package/out-tsc/src/form/ColorPicker.js +28 -40
  42. package/out-tsc/src/form/ColorPicker.js.map +1 -1
  43. package/out-tsc/src/form/Completion.js +44 -53
  44. package/out-tsc/src/form/Completion.js.map +1 -1
  45. package/out-tsc/src/form/Compose.js +7 -8
  46. package/out-tsc/src/form/Compose.js.map +1 -1
  47. package/out-tsc/src/form/ContactSearch.js +3 -4
  48. package/out-tsc/src/form/ContactSearch.js.map +1 -1
  49. package/out-tsc/src/form/DatePicker.js +29 -36
  50. package/out-tsc/src/form/DatePicker.js.map +1 -1
  51. package/out-tsc/src/form/{FormField.js → FieldElement.js} +81 -53
  52. package/out-tsc/src/form/FieldElement.js.map +1 -0
  53. package/out-tsc/src/form/FieldRenderer.js +306 -0
  54. package/out-tsc/src/form/FieldRenderer.js.map +1 -0
  55. package/out-tsc/src/form/ImagePicker.js +122 -126
  56. package/out-tsc/src/form/ImagePicker.js.map +1 -1
  57. package/out-tsc/src/form/KeyValueEditor.js +41 -37
  58. package/out-tsc/src/form/KeyValueEditor.js.map +1 -1
  59. package/out-tsc/src/form/MessageEditor.js +55 -63
  60. package/out-tsc/src/form/MessageEditor.js.map +1 -1
  61. package/out-tsc/src/form/TembaSlider.js +3 -3
  62. package/out-tsc/src/form/TembaSlider.js.map +1 -1
  63. package/out-tsc/src/form/TemplateEditor.js +3 -3
  64. package/out-tsc/src/form/TemplateEditor.js.map +1 -1
  65. package/out-tsc/src/form/TextInput.js +23 -27
  66. package/out-tsc/src/form/TextInput.js.map +1 -1
  67. package/out-tsc/src/form/select/Select.js +57 -35
  68. package/out-tsc/src/form/select/Select.js.map +1 -1
  69. package/out-tsc/src/form/select/UserSelect.js +8 -9
  70. package/out-tsc/src/form/select/UserSelect.js.map +1 -1
  71. package/out-tsc/src/form/select/WorkspaceSelect.js +7 -8
  72. package/out-tsc/src/form/select/WorkspaceSelect.js.map +1 -1
  73. package/out-tsc/src/live/ContactChat.js +62 -44
  74. package/out-tsc/src/live/ContactChat.js.map +1 -1
  75. package/out-tsc/src/live/ContactFieldEditor.js.map +1 -1
  76. package/out-tsc/src/markdown.js +13 -11
  77. package/out-tsc/src/markdown.js.map +1 -1
  78. package/out-tsc/temba-modules.js +3 -2
  79. package/out-tsc/temba-modules.js.map +1 -1
  80. package/out-tsc/test/ActionHelper.js +2 -0
  81. package/out-tsc/test/ActionHelper.js.map +1 -1
  82. package/out-tsc/test/NodeHelper.js +148 -0
  83. package/out-tsc/test/NodeHelper.js.map +1 -0
  84. package/out-tsc/test/actions/call_llm.test.js +103 -0
  85. package/out-tsc/test/actions/call_llm.test.js.map +1 -0
  86. package/out-tsc/test/nodes/split_by_llm_categorize.test.js +532 -0
  87. package/out-tsc/test/nodes/split_by_llm_categorize.test.js.map +1 -0
  88. package/out-tsc/test/nodes/split_by_random.test.js +150 -0
  89. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -0
  90. package/out-tsc/test/nodes/wait_for_digits.test.js +150 -0
  91. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -0
  92. package/out-tsc/test/nodes/wait_for_response.test.js +171 -0
  93. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -0
  94. package/out-tsc/test/temba-add-input-labels.test.js +70 -0
  95. package/out-tsc/test/temba-add-input-labels.test.js.map +1 -0
  96. package/out-tsc/test/temba-checkbox.test.js +16 -0
  97. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  98. package/out-tsc/test/temba-field-renderer.test.js +296 -0
  99. package/out-tsc/test/temba-field-renderer.test.js.map +1 -0
  100. package/out-tsc/test/temba-integration-markdown.test.js +2 -4
  101. package/out-tsc/test/temba-integration-markdown.test.js.map +1 -1
  102. package/out-tsc/test/temba-markdown.test.js +1 -1
  103. package/out-tsc/test/temba-markdown.test.js.map +1 -1
  104. package/out-tsc/test/temba-node-editor.test.js +400 -0
  105. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  106. package/out-tsc/test/temba-select.test.js +6 -3
  107. package/out-tsc/test/temba-select.test.js.map +1 -1
  108. package/out-tsc/test/temba-slider.test.js +0 -1
  109. package/out-tsc/test/temba-slider.test.js.map +1 -1
  110. package/out-tsc/test/temba-webchat.test.js +1 -1
  111. package/out-tsc/test/temba-webchat.test.js.map +1 -1
  112. package/package.json +1 -1
  113. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  114. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  115. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  116. package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
  117. package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
  118. package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
  119. package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
  120. package/screenshots/truth/actions/call_llm/render/information-extraction.png +0 -0
  121. package/screenshots/truth/actions/call_llm/render/sentiment-analysis.png +0 -0
  122. package/screenshots/truth/actions/call_llm/render/summarization.png +0 -0
  123. package/screenshots/truth/actions/call_llm/render/translation-task.png +0 -0
  124. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  125. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  126. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  127. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  128. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  129. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  130. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  131. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  132. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  133. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  134. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  135. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  136. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  137. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  138. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  139. package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
  140. package/screenshots/truth/checkbox/checkbox-no-label-no-background-hover.png +0 -0
  141. package/screenshots/truth/checkbox/checkbox-with-help-text.png +0 -0
  142. package/screenshots/truth/checkbox/checked.png +0 -0
  143. package/screenshots/truth/checkbox/default.png +0 -0
  144. package/screenshots/truth/colorpicker/default.png +0 -0
  145. package/screenshots/truth/colorpicker/focused.png +0 -0
  146. package/screenshots/truth/colorpicker/initialized.png +0 -0
  147. package/screenshots/truth/colorpicker/selected.png +0 -0
  148. package/screenshots/truth/editor/router.png +0 -0
  149. package/screenshots/truth/editor/send_msg.png +0 -0
  150. package/screenshots/truth/editor/set_contact_language.png +0 -0
  151. package/screenshots/truth/editor/set_contact_name.png +0 -0
  152. package/screenshots/truth/editor/set_run_result.png +0 -0
  153. package/screenshots/truth/editor/wait.png +0 -0
  154. package/screenshots/truth/field-renderer/checkbox-checked.png +0 -0
  155. package/screenshots/truth/field-renderer/checkbox-unchecked.png +0 -0
  156. package/screenshots/truth/field-renderer/checkbox-with-errors.png +0 -0
  157. package/screenshots/truth/field-renderer/context-comparison.png +0 -0
  158. package/screenshots/truth/field-renderer/key-value-with-label.png +0 -0
  159. package/screenshots/truth/field-renderer/message-editor-with-label.png +0 -0
  160. package/screenshots/truth/field-renderer/select-multi.png +0 -0
  161. package/screenshots/truth/field-renderer/select-no-label.png +0 -0
  162. package/screenshots/truth/field-renderer/select-with-label.png +0 -0
  163. package/screenshots/truth/field-renderer/text-evaluated.png +0 -0
  164. package/screenshots/truth/field-renderer/text-no-label.png +0 -0
  165. package/screenshots/truth/field-renderer/text-with-errors.png +0 -0
  166. package/screenshots/truth/field-renderer/text-with-label.png +0 -0
  167. package/screenshots/truth/field-renderer/textarea-evaluated.png +0 -0
  168. package/screenshots/truth/field-renderer/textarea-with-label.png +0 -0
  169. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
  170. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  171. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  172. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  173. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  174. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  175. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  176. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  177. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  178. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  179. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  180. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  181. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  182. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  183. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  184. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  185. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  186. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  187. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  188. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  189. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  190. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  191. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  192. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  193. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  194. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  195. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  196. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  197. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  198. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  199. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  200. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  201. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  202. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  203. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  204. package/screenshots/truth/omnibox/selected.png +0 -0
  205. package/screenshots/truth/run-list/basic.png +0 -0
  206. package/screenshots/truth/select/functions.png +0 -0
  207. package/screenshots/truth/select/multi-with-endpoint.png +0 -0
  208. package/screenshots/truth/select/search-enabled.png +0 -0
  209. package/src/events.ts +12 -6
  210. package/src/excellent/helpers.ts +2 -2
  211. package/src/flow/CanvasNode.ts +22 -1
  212. package/src/flow/Editor.ts +12 -1
  213. package/src/flow/NodeEditor.ts +186 -374
  214. package/src/flow/actions/add_input_labels.ts +45 -0
  215. package/src/flow/actions/call_llm.ts +57 -3
  216. package/src/flow/actions/call_webhook.ts +1 -1
  217. package/src/flow/actions/open_ticket.ts +74 -3
  218. package/src/flow/actions/set_run_result.ts +83 -0
  219. package/src/flow/config.ts +4 -0
  220. package/src/flow/nodes/split_by_llm_categorize.ts +277 -0
  221. package/src/flow/nodes/split_by_ticket.ts +19 -0
  222. package/src/flow/nodes/wait_for_response.ts +28 -1
  223. package/src/flow/types.ts +26 -127
  224. package/src/form/ArrayEditor.ts +79 -139
  225. package/src/form/BaseListEditor.ts +4 -4
  226. package/src/form/Checkbox.ts +81 -24
  227. package/src/form/ColorPicker.ts +31 -43
  228. package/src/form/Completion.ts +49 -56
  229. package/src/form/Compose.ts +8 -8
  230. package/src/form/ContactSearch.ts +3 -4
  231. package/src/form/DatePicker.ts +32 -38
  232. package/src/form/{FormField.ts → FieldElement.ts} +108 -55
  233. package/src/form/FieldRenderer.ts +466 -0
  234. package/src/form/ImagePicker.ts +107 -110
  235. package/src/form/KeyValueEditor.ts +43 -39
  236. package/src/form/MessageEditor.ts +61 -67
  237. package/src/form/TembaSlider.ts +3 -3
  238. package/src/form/TemplateEditor.ts +3 -3
  239. package/src/form/TextInput.ts +26 -29
  240. package/src/form/select/Select.ts +63 -37
  241. package/src/form/select/UserSelect.ts +10 -11
  242. package/src/form/select/WorkspaceSelect.ts +9 -10
  243. package/src/live/ContactChat.ts +62 -47
  244. package/src/live/ContactFieldEditor.ts +2 -2
  245. package/src/markdown.ts +19 -11
  246. package/src/store/flow-definition.d.ts +5 -2
  247. package/static/api/labels.json +31 -0
  248. package/static/api/topics.json +24 -9
  249. package/static/api/users.json +35 -16
  250. package/static/css/temba-components.css +3 -3
  251. package/stress-test.js +18 -13
  252. package/temba-modules.ts +3 -2
  253. package/test/ActionHelper.ts +2 -0
  254. package/test/NodeHelper.ts +184 -0
  255. package/test/actions/call_llm.test.ts +137 -0
  256. package/test/nodes/README.md +78 -0
  257. package/test/nodes/split_by_llm_categorize.test.ts +698 -0
  258. package/test/nodes/split_by_random.test.ts +177 -0
  259. package/test/nodes/wait_for_digits.test.ts +176 -0
  260. package/test/nodes/wait_for_response.test.ts +206 -0
  261. package/test/temba-add-input-labels.test.ts +87 -0
  262. package/test/temba-checkbox.test.ts +26 -0
  263. package/test/temba-field-renderer.test.ts +482 -0
  264. package/test/temba-integration-markdown.test.ts +2 -4
  265. package/test/temba-markdown.test.ts +1 -1
  266. package/test/temba-node-editor.test.ts +496 -0
  267. package/test/temba-select.test.ts +6 -6
  268. package/test/temba-slider.test.ts +0 -1
  269. package/test/temba-webchat.test.ts +1 -1
  270. package/test-assets/contacts/history.json +7 -20
  271. package/test-assets/select/llms.json +18 -0
  272. package/web-dev-mock.mjs +96 -6
  273. package/web-dev-server.config.mjs +29 -7
  274. package/out-tsc/src/form/FormElement.js +0 -67
  275. package/out-tsc/src/form/FormElement.js.map +0 -1
  276. package/out-tsc/src/form/FormField.js.map +0 -1
  277. package/out-tsc/test/temba-formfield.test.js +0 -94
  278. package/out-tsc/test/temba-formfield.test.js.map +0 -1
  279. package/src/form/FormElement.ts +0 -69
  280. package/test/temba-flow-editor.test.ts.backup +0 -563
  281. package/test/temba-formfield.test.ts +0 -121
  282. package/test/temba-utils-index.test.ts.backup +0 -1737
@@ -5,6 +5,8 @@ import { RapidElement } from '../RapidElement';
5
5
  import { NODE_CONFIG, ACTION_CONFIG } from './config';
6
6
  import { CustomEventType } from '../interfaces';
7
7
  import { generateUUID } from '../utils';
8
+ import { FieldRenderer } from '../form/FieldRenderer';
9
+ import { renderMarkdownInline } from '../markdown';
8
10
  export class NodeEditor extends RapidElement {
9
11
  constructor() {
10
12
  super(...arguments);
@@ -25,9 +27,9 @@ export class NodeEditor extends RapidElement {
25
27
  min-width: 400px;
26
28
  padding-bottom: 40px;
27
29
 
28
- --color-bubble-bg: rgba(255, 255, 255, 0.8);
29
- --color-bubble-border: #999;
30
- --color-bubble-text: #777;
30
+ --color-bubble-bg: rgba(var(--primary-rgb), 0.7);
31
+ --color-bubble-border: rgba(0, 0, 0, 0.2);
32
+ --color-bubble-text: #fff;
31
33
  }
32
34
 
33
35
  .form-field {
@@ -97,8 +99,8 @@ export class NodeEditor extends RapidElement {
97
99
  }
98
100
 
99
101
  .form-group.has-bubble {
100
- border-width: 1px;
101
- border-color: var(--color-bubble-border, #aaa);
102
+ border-width: 2px;
103
+ border-color: rgba(var(--primary-rgb), 0.5);
102
104
  }
103
105
 
104
106
  .form-group-header {
@@ -114,6 +116,7 @@ export class NodeEditor extends RapidElement {
114
116
  }
115
117
 
116
118
  .form-group.has-bubble .form-group-header {
119
+ background: rgba(var(--primary-rgb), 0.1);
117
120
  }
118
121
 
119
122
  .collapsed .form-group-header {
@@ -262,11 +265,19 @@ export class NodeEditor extends RapidElement {
262
265
  }
263
266
  updated(changedProperties) {
264
267
  super.updated(changedProperties);
265
- if (changedProperties.has('node') || changedProperties.has('action')) {
266
- if (this.node || this.action) {
268
+ if (changedProperties.has('node') ||
269
+ changedProperties.has('action') ||
270
+ changedProperties.has('nodeUI')) {
271
+ // For action editing, we only need the action
272
+ if (this.action && (!this.node || !this.nodeUI)) {
267
273
  this.openDialog();
268
274
  }
269
- else {
275
+ // For node editing, we need both node and nodeUI
276
+ else if (this.node && this.nodeUI) {
277
+ this.openDialog();
278
+ }
279
+ // If we don't have the required data, close the dialog
280
+ else if (!this.action && (!this.node || !this.nodeUI)) {
270
281
  this.isOpen = false;
271
282
  }
272
283
  }
@@ -284,7 +295,8 @@ export class NodeEditor extends RapidElement {
284
295
  this.groupHoverState = {};
285
296
  }
286
297
  initializeFormData() {
287
- if (this.action) {
298
+ const nodeConfig = this.getNodeConfig();
299
+ if ((!nodeConfig || nodeConfig.type === 'execute_actions') && this.action) {
288
300
  // Action editing mode - use action config
289
301
  const actionConfig = ACTION_CONFIG[this.action.type];
290
302
  if (actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.toFormData) {
@@ -292,8 +304,6 @@ export class NodeEditor extends RapidElement {
292
304
  }
293
305
  else {
294
306
  this.formData = { ...this.action };
295
- // Apply smart transformations for select fields that expect {name, value} format
296
- this.applySmartSelectTransformations(actionConfig);
297
307
  }
298
308
  // Convert Record objects to array format for key-value editors
299
309
  this.processFormDataForEditing();
@@ -345,45 +355,31 @@ export class NodeEditor extends RapidElement {
345
355
  });
346
356
  this.formData = processed;
347
357
  }
348
- applySmartSelectTransformations(actionConfig) {
349
- if (!actionConfig)
350
- return;
351
- const fields = actionConfig.form;
352
- if (!fields)
353
- return;
354
- Object.entries(fields).forEach(([fieldName, fieldConfig]) => {
355
- if (this.shouldApplySmartSelectTransformation(fieldName, fieldConfig)) {
356
- const value = this.formData[fieldName];
357
- if (Array.isArray(value) &&
358
- value.length > 0 &&
359
- typeof value[0] === 'string') {
360
- // Transform string array to select options format
361
- this.formData[fieldName] = value.map((item) => ({
362
- name: item,
363
- value: item
364
- }));
365
- }
366
- }
367
- });
368
- }
369
- shouldApplySmartSelectTransformation(fieldName, fieldConfig) {
370
- var _a;
371
- const selectConfig = fieldConfig;
372
- return ((fieldConfig.type === 'select' &&
373
- (selectConfig.multi || selectConfig.tags) &&
374
- // Don't transform if already has explicit transformations
375
- !this.action) ||
376
- !((_a = ACTION_CONFIG[this.action.type]) === null || _a === void 0 ? void 0 : _a.toFormData));
377
- }
378
358
  isKeyValueField(fieldName) {
379
359
  var _a;
380
360
  // Check if this field is configured as a key-value type
361
+ const config = this.getConfig();
362
+ const fields = config === null || config === void 0 ? void 0 : config.form;
363
+ return ((_a = fields === null || fields === void 0 ? void 0 : fields[fieldName]) === null || _a === void 0 ? void 0 : _a.type) === 'key-value';
364
+ }
365
+ getConfig() {
366
+ // If we have a node and nodeUI, check if we should use node config
367
+ if (this.node && this.nodeUI) {
368
+ const nodeConfig = this.getNodeConfig();
369
+ // For execute_actions nodes, defer to action editing if an action is selected
370
+ if (this.nodeUI.type === 'execute_actions' && this.action) {
371
+ return ACTION_CONFIG[this.action.type] || null;
372
+ }
373
+ // For all other nodes with a config, use the node config
374
+ if (nodeConfig) {
375
+ return nodeConfig;
376
+ }
377
+ }
378
+ // Fall back to action config if no node config or for pure action editing
381
379
  if (this.action) {
382
- const actionConfig = ACTION_CONFIG[this.action.type];
383
- const fields = actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.form;
384
- return ((_a = fields === null || fields === void 0 ? void 0 : fields[fieldName]) === null || _a === void 0 ? void 0 : _a.type) === 'key-value';
380
+ return ACTION_CONFIG[this.action.type] || null;
385
381
  }
386
- return false;
382
+ return null;
387
383
  }
388
384
  getNodeConfig() {
389
385
  if (!this.nodeUI)
@@ -392,17 +388,8 @@ export class NodeEditor extends RapidElement {
392
388
  return this.nodeUI.type ? NODE_CONFIG[this.nodeUI.type] : null;
393
389
  }
394
390
  getHeaderColor() {
395
- if (this.action) {
396
- // Action editing mode
397
- const actionConfig = ACTION_CONFIG[this.action.type];
398
- return (actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.color) || '#666666';
399
- }
400
- else if (this.node) {
401
- // Node editing mode
402
- const nodeConfig = this.getNodeConfig();
403
- return (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.color) || '#666666';
404
- }
405
- return '#666666';
391
+ const config = this.getConfig();
392
+ return (config === null || config === void 0 ? void 0 : config.color) || '#666666';
406
393
  }
407
394
  handleDialogButtonClick(event) {
408
395
  const button = event.detail.button;
@@ -483,12 +470,11 @@ export class NodeEditor extends RapidElement {
483
470
  }
484
471
  validateForm() {
485
472
  const errors = {};
486
- if (this.action) {
487
- // Action validation using fields configuration
488
- const actionConfig = ACTION_CONFIG[this.action.type];
473
+ const config = this.getConfig();
474
+ if (config) {
489
475
  // Check if new field configuration system is available
490
- if (actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.form) {
491
- Object.entries(actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.form).forEach(([fieldName, fieldConfig]) => {
476
+ if (config.form) {
477
+ Object.entries(config.form).forEach(([fieldName, fieldConfig]) => {
492
478
  const value = this.formData[fieldName];
493
479
  // Check required fields
494
480
  if (fieldConfig.required &&
@@ -509,38 +495,26 @@ export class NodeEditor extends RapidElement {
509
495
  }
510
496
  });
511
497
  }
498
+ // Universal validation for category arrays to check for reserved names
499
+ this.validateCategoryNames(errors);
512
500
  // Run custom validation if available
513
- if (actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.validate) {
514
- // Convert form data back to action for validation
515
- let actionForValidation;
516
- if (actionConfig.sanitize) {
517
- actionConfig.sanitize(this.formData);
501
+ if (config.validate) {
502
+ if (config.sanitize) {
503
+ config.sanitize(this.formData);
518
504
  }
519
- if (actionConfig.fromFormData) {
520
- actionForValidation = actionConfig.fromFormData(this.formData);
505
+ let customValidation;
506
+ if (this.action) {
507
+ customValidation = config.validate({
508
+ ...this.action,
509
+ ...this.formData
510
+ });
521
511
  }
522
512
  else {
523
- actionForValidation = { ...this.action, ...this.formData };
513
+ customValidation = config.validate(this.formData);
524
514
  }
525
- const customValidation = actionConfig.validate(actionForValidation);
526
515
  Object.assign(errors, customValidation.errors);
527
516
  }
528
517
  }
529
- else if (this.node) {
530
- // Node validation
531
- const nodeConfig = this.getNodeConfig();
532
- // Check required fields from node properties
533
- if (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.properties) {
534
- Object.entries(nodeConfig.properties).forEach(([fieldName, fieldConfig]) => {
535
- const value = this.formData[fieldName];
536
- // Check required fields
537
- if (fieldConfig.required &&
538
- (!value || (Array.isArray(value) && value.length === 0))) {
539
- errors[fieldName] = `${fieldConfig.label || fieldName} is required`;
540
- }
541
- });
542
- }
543
- }
544
518
  // Validate key-value fields for unique keys
545
519
  this.validateKeyValueUniqueness(errors);
546
520
  return {
@@ -579,6 +553,34 @@ export class NodeEditor extends RapidElement {
579
553
  }
580
554
  });
581
555
  }
556
+ validateCategoryNames(errors) {
557
+ // Universal validation for category names across all node types
558
+ // Prevents use of reserved category names that have special meaning in the system
559
+ // Define reserved category names (case-insensitive)
560
+ const reservedNames = [
561
+ 'other',
562
+ 'failure',
563
+ 'success',
564
+ 'all responses',
565
+ 'no response'
566
+ ];
567
+ // Check all form fields for category arrays
568
+ Object.entries(this.formData).forEach(([fieldName, value]) => {
569
+ if (Array.isArray(value) && fieldName === 'categories') {
570
+ const categories = value.filter((item) => (item === null || item === void 0 ? void 0 : item.name) && item.name.trim() !== '');
571
+ // Check for reserved names
572
+ const reservedUsed = categories
573
+ .filter((item) => {
574
+ const lowerName = item.name.trim().toLowerCase();
575
+ return reservedNames.includes(lowerName);
576
+ })
577
+ .map((item) => item.name.trim()); // Preserve original case
578
+ if (reservedUsed.length > 0) {
579
+ errors[fieldName] = `Reserved category names cannot be used: ${reservedUsed.join(', ')}`;
580
+ }
581
+ }
582
+ });
583
+ }
582
584
  formDataToNode(formData = this.formData) {
583
585
  var _a;
584
586
  if (!this.node)
@@ -720,29 +722,10 @@ export class NodeEditor extends RapidElement {
720
722
  return actionConfig.fromFormData(formData);
721
723
  }
722
724
  else {
723
- // Apply smart select transformations in reverse and provide default 1:1 mapping
724
- const processedFormData = this.reverseSmartSelectTransformations(formData, actionConfig);
725
- return { ...this.action, ...processedFormData };
725
+ // Default 1:1 mapping
726
+ return { ...this.action, ...formData };
726
727
  }
727
728
  }
728
- reverseSmartSelectTransformations(formData, actionConfig) {
729
- if (!actionConfig || !actionConfig.form)
730
- return formData;
731
- const processed = { ...formData };
732
- Object.entries(actionConfig.form).forEach(([fieldName, fieldConfig]) => {
733
- if (this.shouldApplySmartSelectTransformation(fieldName, fieldConfig)) {
734
- const value = processed[fieldName];
735
- if (Array.isArray(value) &&
736
- value.length > 0 &&
737
- typeof value[0] === 'object' &&
738
- 'value' in value[0]) {
739
- // Transform select options format back to string array
740
- processed[fieldName] = value.map((item) => item.value || item.name || item);
741
- }
742
- }
743
- });
744
- return processed;
745
- }
746
729
  handleFormFieldChange(propertyName, event) {
747
730
  const target = event.target;
748
731
  let value;
@@ -778,9 +761,7 @@ export class NodeEditor extends RapidElement {
778
761
  this.requestUpdate();
779
762
  }
780
763
  updateGroupCollapseStates() {
781
- if (!this.action)
782
- return;
783
- const config = ACTION_CONFIG[this.action.type];
764
+ const config = this.getConfig();
784
765
  if (!(config === null || config === void 0 ? void 0 : config.layout))
785
766
  return;
786
767
  this.updateGroupCollapseStatesRecursive(config.layout);
@@ -810,9 +791,7 @@ export class NodeEditor extends RapidElement {
810
791
  });
811
792
  }
812
793
  updateComputedFields(changedFieldName) {
813
- if (!this.action)
814
- return;
815
- const config = ACTION_CONFIG[this.action.type];
794
+ const config = this.getConfig();
816
795
  if (!(config === null || config === void 0 ? void 0 : config.form))
817
796
  return;
818
797
  // Check all fields to see if any depend on the changed field
@@ -859,174 +838,36 @@ export class NodeEditor extends RapidElement {
859
838
  return fieldContent;
860
839
  }
861
840
  renderFieldContent(fieldName, config, value, errors) {
862
- var _a;
863
- switch (config.type) {
864
- case 'text':
865
- return html `<temba-textinput
866
- name="${fieldName}"
867
- label="${config.label}"
868
- ?required="${config.required}"
869
- .errors="${errors}"
870
- .value="${value || ''}"
871
- placeholder="${config.placeholder || ''}"
872
- .helpText="${config.helpText || ''}"
873
- @input="${(e) => this.handleFormFieldChange(fieldName, e)}"
874
- ></temba-textinput>`;
875
- case 'textarea': {
876
- const textareaConfig = config;
877
- const minHeightStyle = textareaConfig.minHeight
878
- ? `--textarea-min-height: ${textareaConfig.minHeight}px;`
879
- : '';
880
- if (config.evaluated) {
881
- return html `<temba-completion
882
- name="${fieldName}"
883
- label="${config.label}"
884
- ?required="${config.required}"
885
- .errors="${errors}"
886
- .value="${value || ''}"
887
- placeholder="${config.placeholder || ''}"
888
- textarea
889
- expressions="session"
890
- style="${minHeightStyle}"
891
- .helpText="${config.helpText || ''}"
892
- @input="${(e) => this.handleFormFieldChange(fieldName, e)}"
893
- ></temba-completion>`;
841
+ // Use FieldRenderer for consistent field rendering
842
+ return FieldRenderer.renderField(fieldName, config, value, {
843
+ errors,
844
+ onChange: (e) => {
845
+ // Handle different change event types
846
+ if (fieldName && config.type === 'key-value') {
847
+ // Special handling for key-value editor
848
+ const customEvent = e;
849
+ if (customEvent.detail) {
850
+ this.handleNewFieldChange(fieldName, customEvent.detail.value);
851
+ }
852
+ }
853
+ else if (fieldName && config.type === 'array') {
854
+ // Special handling for array editor
855
+ this.handleNewFieldChange(fieldName, e.target.value);
856
+ }
857
+ else if (fieldName && config.type === 'message-editor') {
858
+ // Special handling for message editor
859
+ this.handleMessageEditorChange(fieldName, e);
894
860
  }
895
861
  else {
896
- return html `<temba-textinput
897
- name="${fieldName}"
898
- label="${config.label}"
899
- ?required="${config.required}"
900
- .errors="${errors}"
901
- .value="${value || ''}"
902
- placeholder="${config.placeholder || ''}"
903
- textarea
904
- .rows="${textareaConfig.rows || 3}"
905
- style="${minHeightStyle}"
906
- .helpText="${config.helpText || ''}"
907
- @input="${(e) => this.handleFormFieldChange(fieldName, e)}"
908
- ></temba-textinput>`;
862
+ // Default handling for most field types
863
+ this.handleFormFieldChange(fieldName, e);
909
864
  }
865
+ },
866
+ showLabel: true,
867
+ additionalData: {
868
+ attachments: this.formData.attachments || []
910
869
  }
911
- case 'select': {
912
- const selectConfig = config;
913
- return html `<temba-select
914
- name="${fieldName}"
915
- label="${config.label}"
916
- ?required="${config.required}"
917
- .errors="${errors}"
918
- .values="${value || (selectConfig.multi ? [] : '')}"
919
- ?multi="${selectConfig.multi}"
920
- ?searchable="${selectConfig.searchable}"
921
- ?tags="${selectConfig.tags}"
922
- ?emails="${selectConfig.emails}"
923
- placeholder="${selectConfig.placeholder || ''}"
924
- maxItems="${selectConfig.maxItems || 0}"
925
- valueKey="${selectConfig.valueKey || 'value'}"
926
- nameKey="${selectConfig.nameKey || 'name'}"
927
- endpoint="${selectConfig.endpoint || ''}"
928
- .helpText="${config.helpText || ''}"
929
- flavor="${selectConfig.flavor || 'small'}"
930
- @change="${(e) => this.handleFormFieldChange(fieldName, e)}"
931
- >
932
- ${(_a = selectConfig.options) === null || _a === void 0 ? void 0 : _a.map((option) => {
933
- if (typeof option === 'string') {
934
- return html `<temba-option
935
- name="${option}"
936
- value="${option}"
937
- ></temba-option>`;
938
- }
939
- else {
940
- return html `<temba-option
941
- name="${option.label || option.name}"
942
- value="${option.value}"
943
- ></temba-option>`;
944
- }
945
- })}
946
- </temba-select>`;
947
- }
948
- case 'key-value':
949
- return html `<div class="form-field">
950
- <label>${config.label}${config.required ? ' *' : ''}</label>
951
- <temba-key-value-editor
952
- name="${fieldName}"
953
- .value="${value || []}"
954
- .sortable="${config.sortable}"
955
- .keyPlaceholder="${config.keyPlaceholder || 'Key'}"
956
- .valuePlaceholder="${config.valuePlaceholder || 'Value'}"
957
- .minRows="${config.minRows || 0}"
958
- @change="${(e) => {
959
- if (e.detail) {
960
- this.handleNewFieldChange(fieldName, e.detail.value);
961
- }
962
- }}"
963
- ></temba-key-value-editor>
964
- ${errors.length
965
- ? html `<div class="field-errors">${errors.join(', ')}</div>`
966
- : ''}
967
- </div>`;
968
- case 'array':
969
- return html `<div class="form-field">
970
- <label>${config.label}${config.required ? ' *' : ''}</label>
971
- <temba-array-editor
972
- .value="${value || []}"
973
- .itemConfig="${config.itemConfig}"
974
- .sortable="${config.sortable}"
975
- .itemLabel="${config.itemLabel || 'Item'}"
976
- .minItems="${config.minItems || 0}"
977
- .maxItems="${config.maxItems || 0}"
978
- .onItemChange="${config.onItemChange}"
979
- .isEmptyItemFn="${config.isEmptyItem}"
980
- @change="${(e) => this.handleNewFieldChange(fieldName, e.target.value)}"
981
- ></temba-array-editor>
982
- ${errors.length
983
- ? html `<div class="field-errors">${errors.join(', ')}</div>`
984
- : ''}
985
- </div>`;
986
- case 'checkbox': {
987
- const checkboxConfig = config;
988
- return html `<div class="form-field">
989
- <temba-checkbox
990
- name="${fieldName}"
991
- label="${config.label}"
992
- .helpText="${config.helpText || ''}"
993
- ?required="${config.required}"
994
- .errors="${errors}"
995
- ?checked="${value || false}"
996
- size="${checkboxConfig.size || 1.2}"
997
- animateChange="${checkboxConfig.animateChange || 'pulse'}"
998
- @change="${(e) => this.handleFormFieldChange(fieldName, e)}"
999
- ></temba-checkbox>
1000
- ${errors.length
1001
- ? html `<div class="field-errors">${errors.join(', ')}</div>`
1002
- : ''}
1003
- </div>`;
1004
- }
1005
- case 'message-editor': {
1006
- const messageConfig = config;
1007
- return html `<temba-message-editor
1008
- name="${fieldName}"
1009
- label="${config.label}"
1010
- ?required="${config.required}"
1011
- .errors="${errors}"
1012
- .value="${value || ''}"
1013
- .attachments="${this.formData.attachments || []}"
1014
- placeholder="${messageConfig.placeholder || ''}"
1015
- .helpText="${config.helpText || ''}"
1016
- ?autogrow="${messageConfig.autogrow}"
1017
- ?gsm="${messageConfig.gsm}"
1018
- ?disableCompletion="${messageConfig.disableCompletion}"
1019
- counter="${messageConfig.counter || ''}"
1020
- accept="${messageConfig.accept || ''}"
1021
- endpoint="${messageConfig.endpoint || ''}"
1022
- max-attachments="${messageConfig.maxAttachments || 3}"
1023
- minHeight="${messageConfig.minHeight || 60}"
1024
- @change="${(e) => this.handleMessageEditorChange(fieldName, e)}"
1025
- ></temba-message-editor>`;
1026
- }
1027
- default:
1028
- return html `<div>Unsupported field type: ${config.type}</div>`;
1029
- }
870
+ });
1030
871
  }
1031
872
  handleGroupToggle(groupLabel) {
1032
873
  this.groupCollapseState = {
@@ -1047,9 +888,7 @@ export class NodeEditor extends RapidElement {
1047
888
  };
1048
889
  }
1049
890
  expandGroupsWithErrors(errors) {
1050
- if (!this.action)
1051
- return;
1052
- const config = ACTION_CONFIG[this.action.type];
891
+ const config = this.getConfig();
1053
892
  if (!(config === null || config === void 0 ? void 0 : config.layout))
1054
893
  return;
1055
894
  const errorFields = new Set(Object.keys(errors));
@@ -1184,7 +1023,9 @@ export class NodeEditor extends RapidElement {
1184
1023
  <div class="form-group-info">
1185
1024
  <div class="form-group-title">${label}</div>
1186
1025
  ${helpText
1187
- ? html `<div class="form-group-help">${helpText}</div>`
1026
+ ? html `<div class="form-group-help">
1027
+ ${renderMarkdownInline(helpText)}
1028
+ </div>`
1188
1029
  : ''}
1189
1030
  </div>
1190
1031
  ${groupHasErrors
@@ -1288,12 +1129,9 @@ export class NodeEditor extends RapidElement {
1288
1129
  this.requestUpdate();
1289
1130
  }
1290
1131
  renderFields() {
1291
- if (!this.action) {
1292
- return html ` <div>No action selected</div> `;
1293
- }
1294
- const config = ACTION_CONFIG[this.action.type];
1132
+ const config = this.getConfig();
1295
1133
  if (!config) {
1296
- return html ` <div>No configuration available for this action</div> `;
1134
+ return html ` <div>No configuration available</div> `;
1297
1135
  }
1298
1136
  // Use the new fields configuration system
1299
1137
  if (config.form) {
@@ -1319,7 +1157,13 @@ export class NodeEditor extends RapidElement {
1319
1157
  `;
1320
1158
  }
1321
1159
  }
1322
- return html ` <div>No form configuration available</div> `;
1160
+ // Fallback for configs without form configuration
1161
+ if (this.action) {
1162
+ return html ` <div>No form configuration available for this action</div> `;
1163
+ }
1164
+ else {
1165
+ return html ` <div>No form configuration available for this node</div> `;
1166
+ }
1323
1167
  }
1324
1168
  renderActionSection() {
1325
1169
  if (!this.node || this.node.actions.length === 0) {
@@ -1383,11 +1227,10 @@ export class NodeEditor extends RapidElement {
1383
1227
  return html ``;
1384
1228
  }
1385
1229
  const headerColor = this.getHeaderColor();
1386
- const nodeConfig = this.getNodeConfig();
1387
- const actionConfig = ACTION_CONFIG[(_a = this.action) === null || _a === void 0 ? void 0 : _a.type];
1230
+ const config = this.getConfig();
1388
1231
  return html `
1389
1232
  <temba-dialog
1390
- header="${(actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.name) || (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.name) || 'Edit'}"
1233
+ header="${(config === null || config === void 0 ? void 0 : config.name) || 'Edit'}"
1391
1234
  .open="${this.isOpen}"
1392
1235
  @temba-button-clicked=${this.handleDialogButtonClick}
1393
1236
  primaryButtonName="Save"
@@ -1396,7 +1239,7 @@ export class NodeEditor extends RapidElement {
1396
1239
  >
1397
1240
  <div class="node-editor-form">
1398
1241
  ${this.renderFields()}
1399
- ${((_b = nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.router) === null || _b === void 0 ? void 0 : _b.configurable)
1242
+ ${((_b = (_a = this.getNodeConfig()) === null || _a === void 0 ? void 0 : _a.router) === null || _b === void 0 ? void 0 : _b.configurable)
1400
1243
  ? this.renderRouterSection()
1401
1244
  : null}
1402
1245
  </div>