@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
@@ -10,17 +10,11 @@ import {
10
10
  FieldConfig,
11
11
  ActionConfig
12
12
  } from './config';
13
- import {
14
- SelectFieldConfig,
15
- CheckboxFieldConfig,
16
- TextareaFieldConfig,
17
- MessageEditorFieldConfig,
18
- LayoutItem,
19
- RowLayoutConfig,
20
- GroupLayoutConfig
21
- } from './types';
13
+ import { LayoutItem, RowLayoutConfig, GroupLayoutConfig } from './types';
22
14
  import { CustomEventType } from '../interfaces';
23
15
  import { generateUUID } from '../utils';
16
+ import { FieldRenderer } from '../form/FieldRenderer';
17
+ import { renderMarkdownInline } from '../markdown';
24
18
 
25
19
  export class NodeEditor extends RapidElement {
26
20
  static get styles() {
@@ -33,9 +27,9 @@ export class NodeEditor extends RapidElement {
33
27
  min-width: 400px;
34
28
  padding-bottom: 40px;
35
29
 
36
- --color-bubble-bg: rgba(255, 255, 255, 0.8);
37
- --color-bubble-border: #999;
38
- --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;
39
33
  }
40
34
 
41
35
  .form-field {
@@ -105,8 +99,8 @@ export class NodeEditor extends RapidElement {
105
99
  }
106
100
 
107
101
  .form-group.has-bubble {
108
- border-width: 1px;
109
- border-color: var(--color-bubble-border, #aaa);
102
+ border-width: 2px;
103
+ border-color: rgba(var(--primary-rgb), 0.5);
110
104
  }
111
105
 
112
106
  .form-group-header {
@@ -122,6 +116,7 @@ export class NodeEditor extends RapidElement {
122
116
  }
123
117
 
124
118
  .form-group.has-bubble .form-group-header {
119
+ background: rgba(var(--primary-rgb), 0.1);
125
120
  }
126
121
 
127
122
  .collapsed .form-group-header {
@@ -299,10 +294,21 @@ export class NodeEditor extends RapidElement {
299
294
 
300
295
  updated(changedProperties: Map<string | number | symbol, unknown>): void {
301
296
  super.updated(changedProperties);
302
- if (changedProperties.has('node') || changedProperties.has('action')) {
303
- if (this.node || this.action) {
297
+ if (
298
+ changedProperties.has('node') ||
299
+ changedProperties.has('action') ||
300
+ changedProperties.has('nodeUI')
301
+ ) {
302
+ // For action editing, we only need the action
303
+ if (this.action && (!this.node || !this.nodeUI)) {
304
304
  this.openDialog();
305
- } else {
305
+ }
306
+ // For node editing, we need both node and nodeUI
307
+ else if (this.node && this.nodeUI) {
308
+ this.openDialog();
309
+ }
310
+ // If we don't have the required data, close the dialog
311
+ else if (!this.action && (!this.node || !this.nodeUI)) {
306
312
  this.isOpen = false;
307
313
  }
308
314
  }
@@ -323,7 +329,9 @@ export class NodeEditor extends RapidElement {
323
329
  }
324
330
 
325
331
  private initializeFormData(): void {
326
- if (this.action) {
332
+ const nodeConfig = this.getNodeConfig();
333
+
334
+ if ((!nodeConfig || nodeConfig.type === 'execute_actions') && this.action) {
327
335
  // Action editing mode - use action config
328
336
  const actionConfig = ACTION_CONFIG[this.action.type];
329
337
 
@@ -331,8 +339,6 @@ export class NodeEditor extends RapidElement {
331
339
  this.formData = actionConfig.toFormData(this.action);
332
340
  } else {
333
341
  this.formData = { ...this.action };
334
- // Apply smart transformations for select fields that expect {name, value} format
335
- this.applySmartSelectTransformations(actionConfig);
336
342
  }
337
343
 
338
344
  // Convert Record objects to array format for key-value editors
@@ -390,52 +396,35 @@ export class NodeEditor extends RapidElement {
390
396
  this.formData = processed;
391
397
  }
392
398
 
393
- private applySmartSelectTransformations(actionConfig: ActionConfig): void {
394
- if (!actionConfig) return;
399
+ private isKeyValueField(fieldName: string): boolean {
400
+ // Check if this field is configured as a key-value type
401
+ const config = this.getConfig();
402
+ const fields = config?.form;
403
+ return fields?.[fieldName]?.type === 'key-value';
404
+ }
395
405
 
396
- const fields = actionConfig.form;
397
- if (!fields) return;
406
+ private getConfig(): ActionConfig | NodeConfig | null {
407
+ // If we have a node and nodeUI, check if we should use node config
408
+ if (this.node && this.nodeUI) {
409
+ const nodeConfig = this.getNodeConfig();
398
410
 
399
- Object.entries(fields).forEach(([fieldName, fieldConfig]) => {
400
- if (this.shouldApplySmartSelectTransformation(fieldName, fieldConfig)) {
401
- const value = this.formData[fieldName];
402
- if (
403
- Array.isArray(value) &&
404
- value.length > 0 &&
405
- typeof value[0] === 'string'
406
- ) {
407
- // Transform string array to select options format
408
- this.formData[fieldName] = value.map((item: string) => ({
409
- name: item,
410
- value: item
411
- }));
412
- }
411
+ // For execute_actions nodes, defer to action editing if an action is selected
412
+ if (this.nodeUI.type === 'execute_actions' && this.action) {
413
+ return ACTION_CONFIG[this.action.type] || null;
413
414
  }
414
- });
415
- }
416
415
 
417
- private shouldApplySmartSelectTransformation(
418
- fieldName: string,
419
- fieldConfig: any
420
- ): boolean {
421
- const selectConfig = fieldConfig as SelectFieldConfig;
422
- return (
423
- (fieldConfig.type === 'select' &&
424
- (selectConfig.multi || selectConfig.tags) &&
425
- // Don't transform if already has explicit transformations
426
- !this.action) ||
427
- !ACTION_CONFIG[this.action.type]?.toFormData
428
- );
429
- }
416
+ // For all other nodes with a config, use the node config
417
+ if (nodeConfig) {
418
+ return nodeConfig;
419
+ }
420
+ }
430
421
 
431
- private isKeyValueField(fieldName: string): boolean {
432
- // Check if this field is configured as a key-value type
422
+ // Fall back to action config if no node config or for pure action editing
433
423
  if (this.action) {
434
- const actionConfig = ACTION_CONFIG[this.action.type];
435
- const fields = actionConfig?.form;
436
- return fields?.[fieldName]?.type === 'key-value';
424
+ return ACTION_CONFIG[this.action.type] || null;
437
425
  }
438
- return false;
426
+
427
+ return null;
439
428
  }
440
429
 
441
430
  private getNodeConfig(): NodeConfig | null {
@@ -445,16 +434,8 @@ export class NodeEditor extends RapidElement {
445
434
  }
446
435
 
447
436
  private getHeaderColor(): string {
448
- if (this.action) {
449
- // Action editing mode
450
- const actionConfig = ACTION_CONFIG[this.action.type];
451
- return actionConfig?.color || '#666666';
452
- } else if (this.node) {
453
- // Node editing mode
454
- const nodeConfig = this.getNodeConfig();
455
- return nodeConfig?.color || '#666666';
456
- }
457
- return '#666666';
437
+ const config = this.getConfig();
438
+ return config?.color || '#666666';
458
439
  }
459
440
 
460
441
  private handleDialogButtonClick(event: CustomEvent): void {
@@ -545,94 +526,70 @@ export class NodeEditor extends RapidElement {
545
526
 
546
527
  private validateForm(): ValidationResult {
547
528
  const errors: { [key: string]: string } = {};
529
+ const config = this.getConfig();
548
530
 
549
- if (this.action) {
550
- // Action validation using fields configuration
551
- const actionConfig = ACTION_CONFIG[this.action.type];
552
-
531
+ if (config) {
553
532
  // Check if new field configuration system is available
554
- if (actionConfig?.form) {
555
- Object.entries(actionConfig?.form).forEach(
556
- ([fieldName, fieldConfig]) => {
557
- const value = this.formData[fieldName];
558
-
559
- // Check required fields
560
- if (
561
- (fieldConfig as any).required &&
562
- (!value || (Array.isArray(value) && value.length === 0))
563
- ) {
564
- errors[fieldName] = `${
565
- (fieldConfig as any).label || fieldName
566
- } is required.`;
567
- }
533
+ if (config.form) {
534
+ Object.entries(config.form).forEach(([fieldName, fieldConfig]) => {
535
+ const value = this.formData[fieldName];
536
+
537
+ // Check required fields
538
+ if (
539
+ (fieldConfig as any).required &&
540
+ (!value || (Array.isArray(value) && value.length === 0))
541
+ ) {
542
+ errors[fieldName] = `${
543
+ (fieldConfig as any).label || fieldName
544
+ } is required.`;
545
+ }
568
546
 
569
- // Check minLength for text fields
570
- if (
571
- typeof value === 'string' &&
572
- (fieldConfig as any).minLength &&
573
- value.length < (fieldConfig as any).minLength
574
- ) {
575
- errors[fieldName] = `${
576
- (fieldConfig as any).label || fieldName
577
- } must be at least ${(fieldConfig as any).minLength} characters`;
578
- }
547
+ // Check minLength for text fields
548
+ if (
549
+ typeof value === 'string' &&
550
+ (fieldConfig as any).minLength &&
551
+ value.length < (fieldConfig as any).minLength
552
+ ) {
553
+ errors[fieldName] = `${
554
+ (fieldConfig as any).label || fieldName
555
+ } must be at least ${(fieldConfig as any).minLength} characters`;
556
+ }
579
557
 
580
- // Check maxLength for text fields
581
- if (
582
- typeof value === 'string' &&
583
- (fieldConfig as any).maxLength &&
584
- value.length > (fieldConfig as any).maxLength
585
- ) {
586
- errors[fieldName] = `${
587
- (fieldConfig as any).label || fieldName
588
- } must be no more than ${
589
- (fieldConfig as any).maxLength
590
- } characters`;
591
- }
558
+ // Check maxLength for text fields
559
+ if (
560
+ typeof value === 'string' &&
561
+ (fieldConfig as any).maxLength &&
562
+ value.length > (fieldConfig as any).maxLength
563
+ ) {
564
+ errors[fieldName] = `${
565
+ (fieldConfig as any).label || fieldName
566
+ } must be no more than ${
567
+ (fieldConfig as any).maxLength
568
+ } characters`;
592
569
  }
593
- );
570
+ });
594
571
  }
595
572
 
596
- // Run custom validation if available
597
- if (actionConfig?.validate) {
598
- // Convert form data back to action for validation
599
- let actionForValidation: Action;
573
+ // Universal validation for category arrays to check for reserved names
574
+ this.validateCategoryNames(errors);
600
575
 
601
- if (actionConfig.sanitize) {
602
- actionConfig.sanitize(this.formData);
576
+ // Run custom validation if available
577
+ if (config.validate) {
578
+ if (config.sanitize) {
579
+ config.sanitize(this.formData);
603
580
  }
604
581
 
605
- if (actionConfig.fromFormData) {
606
- actionForValidation = actionConfig.fromFormData(this.formData);
582
+ let customValidation;
583
+ if (this.action) {
584
+ customValidation = config.validate({
585
+ ...this.action,
586
+ ...this.formData
587
+ });
607
588
  } else {
608
- actionForValidation = { ...this.action, ...this.formData } as Action;
589
+ customValidation = config.validate(this.formData);
609
590
  }
610
-
611
- const customValidation = actionConfig.validate(actionForValidation);
612
591
  Object.assign(errors, customValidation.errors);
613
592
  }
614
- } else if (this.node) {
615
- // Node validation
616
- const nodeConfig = this.getNodeConfig();
617
-
618
- // Check required fields from node properties
619
- if (nodeConfig?.properties) {
620
- Object.entries(nodeConfig.properties).forEach(
621
- ([fieldName, fieldConfig]) => {
622
- const value = this.formData[fieldName];
623
-
624
- // Check required fields
625
- if (
626
- fieldConfig.required &&
627
- (!value || (Array.isArray(value) && value.length === 0))
628
- ) {
629
- errors[fieldName] = `${
630
- fieldConfig.label || fieldName
631
- } is required`;
632
- }
633
- }
634
- );
635
- }
636
593
  }
637
594
 
638
595
  // Validate key-value fields for unique keys
@@ -683,6 +640,44 @@ export class NodeEditor extends RapidElement {
683
640
  });
684
641
  }
685
642
 
643
+ private validateCategoryNames(errors: { [key: string]: string }): void {
644
+ // Universal validation for category names across all node types
645
+ // Prevents use of reserved category names that have special meaning in the system
646
+ // Define reserved category names (case-insensitive)
647
+ const reservedNames = [
648
+ 'other',
649
+ 'failure',
650
+ 'success',
651
+ 'all responses',
652
+ 'no response'
653
+ ];
654
+
655
+ // Check all form fields for category arrays
656
+ Object.entries(this.formData).forEach(([fieldName, value]) => {
657
+ if (Array.isArray(value) && fieldName === 'categories') {
658
+ const categories = value.filter(
659
+ (item: any) => item?.name && item.name.trim() !== ''
660
+ );
661
+
662
+ // Check for reserved names
663
+ const reservedUsed = categories
664
+ .filter((item: any) => {
665
+ const lowerName = item.name.trim().toLowerCase();
666
+ return reservedNames.includes(lowerName);
667
+ })
668
+ .map((item: any) => item.name.trim()); // Preserve original case
669
+
670
+ if (reservedUsed.length > 0) {
671
+ errors[
672
+ fieldName
673
+ ] = `Reserved category names cannot be used: ${reservedUsed.join(
674
+ ', '
675
+ )}`;
676
+ }
677
+ }
678
+ });
679
+ }
680
+
686
681
  private formDataToNode(formData: any = this.formData): Node {
687
682
  if (!this.node) throw new Error('No node to update');
688
683
  let updatedNode: Node = { ...this.node };
@@ -849,42 +844,11 @@ export class NodeEditor extends RapidElement {
849
844
  if (actionConfig?.fromFormData) {
850
845
  return actionConfig.fromFormData(formData);
851
846
  } else {
852
- // Apply smart select transformations in reverse and provide default 1:1 mapping
853
- const processedFormData = this.reverseSmartSelectTransformations(
854
- formData,
855
- actionConfig
856
- );
857
- return { ...this.action, ...processedFormData };
847
+ // Default 1:1 mapping
848
+ return { ...this.action, ...formData };
858
849
  }
859
850
  }
860
851
 
861
- private reverseSmartSelectTransformations(
862
- formData: any,
863
- actionConfig: ActionConfig
864
- ): any {
865
- if (!actionConfig || !actionConfig.form) return formData;
866
- const processed = { ...formData };
867
-
868
- Object.entries(actionConfig.form).forEach(([fieldName, fieldConfig]) => {
869
- if (this.shouldApplySmartSelectTransformation(fieldName, fieldConfig)) {
870
- const value = processed[fieldName];
871
- if (
872
- Array.isArray(value) &&
873
- value.length > 0 &&
874
- typeof value[0] === 'object' &&
875
- 'value' in value[0]
876
- ) {
877
- // Transform select options format back to string array
878
- processed[fieldName] = value.map(
879
- (item: any) => item.value || item.name || item
880
- );
881
- }
882
- }
883
- });
884
-
885
- return processed;
886
- }
887
-
888
852
  private handleFormFieldChange(propertyName: string, event: Event): void {
889
853
  const target = event.target as any;
890
854
  let value: any;
@@ -926,9 +890,7 @@ export class NodeEditor extends RapidElement {
926
890
  }
927
891
 
928
892
  private updateGroupCollapseStates(): void {
929
- if (!this.action) return;
930
-
931
- const config = ACTION_CONFIG[this.action.type];
893
+ const config = this.getConfig();
932
894
  if (!config?.layout) return;
933
895
 
934
896
  this.updateGroupCollapseStatesRecursive(config.layout);
@@ -962,9 +924,7 @@ export class NodeEditor extends RapidElement {
962
924
  }
963
925
 
964
926
  private updateComputedFields(changedFieldName: string): void {
965
- if (!this.action) return;
966
-
967
- const config = ACTION_CONFIG[this.action.type];
927
+ const config = this.getConfig();
968
928
  if (!config?.form) return;
969
929
 
970
930
  // Check all fields to see if any depend on the changed field
@@ -1035,181 +995,33 @@ export class NodeEditor extends RapidElement {
1035
995
  value: any,
1036
996
  errors: string[]
1037
997
  ): TemplateResult {
1038
- switch (config.type) {
1039
- case 'text':
1040
- return html`<temba-textinput
1041
- name="${fieldName}"
1042
- label="${config.label}"
1043
- ?required="${config.required}"
1044
- .errors="${errors}"
1045
- .value="${value || ''}"
1046
- placeholder="${config.placeholder || ''}"
1047
- .helpText="${config.helpText || ''}"
1048
- @input="${(e: Event) => this.handleFormFieldChange(fieldName, e)}"
1049
- ></temba-textinput>`;
1050
-
1051
- case 'textarea': {
1052
- const textareaConfig = config as TextareaFieldConfig;
1053
- const minHeightStyle = textareaConfig.minHeight
1054
- ? `--textarea-min-height: ${textareaConfig.minHeight}px;`
1055
- : '';
1056
-
1057
- if (config.evaluated) {
1058
- return html`<temba-completion
1059
- name="${fieldName}"
1060
- label="${config.label}"
1061
- ?required="${config.required}"
1062
- .errors="${errors}"
1063
- .value="${value || ''}"
1064
- placeholder="${config.placeholder || ''}"
1065
- textarea
1066
- expressions="session"
1067
- style="${minHeightStyle}"
1068
- .helpText="${config.helpText || ''}"
1069
- @input="${(e: Event) => this.handleFormFieldChange(fieldName, e)}"
1070
- ></temba-completion>`;
998
+ // Use FieldRenderer for consistent field rendering
999
+ return FieldRenderer.renderField(fieldName, config, value, {
1000
+ errors,
1001
+ onChange: (e: Event) => {
1002
+ // Handle different change event types
1003
+ if (fieldName && config.type === 'key-value') {
1004
+ // Special handling for key-value editor
1005
+ const customEvent = e as CustomEvent;
1006
+ if (customEvent.detail) {
1007
+ this.handleNewFieldChange(fieldName, customEvent.detail.value);
1008
+ }
1009
+ } else if (fieldName && config.type === 'array') {
1010
+ // Special handling for array editor
1011
+ this.handleNewFieldChange(fieldName, (e.target as any).value);
1012
+ } else if (fieldName && config.type === 'message-editor') {
1013
+ // Special handling for message editor
1014
+ this.handleMessageEditorChange(fieldName, e);
1071
1015
  } else {
1072
- return html`<temba-textinput
1073
- name="${fieldName}"
1074
- label="${config.label}"
1075
- ?required="${config.required}"
1076
- .errors="${errors}"
1077
- .value="${value || ''}"
1078
- placeholder="${config.placeholder || ''}"
1079
- textarea
1080
- .rows="${textareaConfig.rows || 3}"
1081
- style="${minHeightStyle}"
1082
- .helpText="${config.helpText || ''}"
1083
- @input="${(e: Event) => this.handleFormFieldChange(fieldName, e)}"
1084
- ></temba-textinput>`;
1016
+ // Default handling for most field types
1017
+ this.handleFormFieldChange(fieldName, e);
1085
1018
  }
1019
+ },
1020
+ showLabel: true,
1021
+ additionalData: {
1022
+ attachments: this.formData.attachments || []
1086
1023
  }
1087
-
1088
- case 'select': {
1089
- const selectConfig = config as SelectFieldConfig;
1090
- return html`<temba-select
1091
- name="${fieldName}"
1092
- label="${config.label}"
1093
- ?required="${config.required}"
1094
- .errors="${errors}"
1095
- .values="${value || (selectConfig.multi ? [] : '')}"
1096
- ?multi="${selectConfig.multi}"
1097
- ?searchable="${selectConfig.searchable}"
1098
- ?tags="${selectConfig.tags}"
1099
- ?emails="${selectConfig.emails}"
1100
- placeholder="${selectConfig.placeholder || ''}"
1101
- maxItems="${selectConfig.maxItems || 0}"
1102
- valueKey="${selectConfig.valueKey || 'value'}"
1103
- nameKey="${selectConfig.nameKey || 'name'}"
1104
- endpoint="${selectConfig.endpoint || ''}"
1105
- .helpText="${config.helpText || ''}"
1106
- flavor="${selectConfig.flavor || 'small'}"
1107
- @change="${(e: Event) => this.handleFormFieldChange(fieldName, e)}"
1108
- >
1109
- ${selectConfig.options?.map((option: any) => {
1110
- if (typeof option === 'string') {
1111
- return html`<temba-option
1112
- name="${option}"
1113
- value="${option}"
1114
- ></temba-option>`;
1115
- } else {
1116
- return html`<temba-option
1117
- name="${option.label || option.name}"
1118
- value="${option.value}"
1119
- ></temba-option>`;
1120
- }
1121
- })}
1122
- </temba-select>`;
1123
- }
1124
-
1125
- case 'key-value':
1126
- return html`<div class="form-field">
1127
- <label>${config.label}${config.required ? ' *' : ''}</label>
1128
- <temba-key-value-editor
1129
- name="${fieldName}"
1130
- .value="${value || []}"
1131
- .sortable="${config.sortable}"
1132
- .keyPlaceholder="${config.keyPlaceholder || 'Key'}"
1133
- .valuePlaceholder="${config.valuePlaceholder || 'Value'}"
1134
- .minRows="${config.minRows || 0}"
1135
- @change="${(e: CustomEvent) => {
1136
- if (e.detail) {
1137
- this.handleNewFieldChange(fieldName, e.detail.value);
1138
- }
1139
- }}"
1140
- ></temba-key-value-editor>
1141
- ${errors.length
1142
- ? html`<div class="field-errors">${errors.join(', ')}</div>`
1143
- : ''}
1144
- </div>`;
1145
-
1146
- case 'array':
1147
- return html`<div class="form-field">
1148
- <label>${config.label}${config.required ? ' *' : ''}</label>
1149
- <temba-array-editor
1150
- .value="${value || []}"
1151
- .itemConfig="${config.itemConfig}"
1152
- .sortable="${config.sortable}"
1153
- .itemLabel="${config.itemLabel || 'Item'}"
1154
- .minItems="${config.minItems || 0}"
1155
- .maxItems="${config.maxItems || 0}"
1156
- .onItemChange="${config.onItemChange}"
1157
- .isEmptyItemFn="${config.isEmptyItem}"
1158
- @change="${(e: Event) =>
1159
- this.handleNewFieldChange(fieldName, (e.target as any).value)}"
1160
- ></temba-array-editor>
1161
- ${errors.length
1162
- ? html`<div class="field-errors">${errors.join(', ')}</div>`
1163
- : ''}
1164
- </div>`;
1165
-
1166
- case 'checkbox': {
1167
- const checkboxConfig = config as CheckboxFieldConfig;
1168
- return html`<div class="form-field">
1169
- <temba-checkbox
1170
- name="${fieldName}"
1171
- label="${config.label}"
1172
- .helpText="${config.helpText || ''}"
1173
- ?required="${config.required}"
1174
- .errors="${errors}"
1175
- ?checked="${value || false}"
1176
- size="${checkboxConfig.size || 1.2}"
1177
- animateChange="${checkboxConfig.animateChange || 'pulse'}"
1178
- @change="${(e: Event) => this.handleFormFieldChange(fieldName, e)}"
1179
- ></temba-checkbox>
1180
- ${errors.length
1181
- ? html`<div class="field-errors">${errors.join(', ')}</div>`
1182
- : ''}
1183
- </div>`;
1184
- }
1185
-
1186
- case 'message-editor': {
1187
- const messageConfig = config as MessageEditorFieldConfig;
1188
- return html`<temba-message-editor
1189
- name="${fieldName}"
1190
- label="${config.label}"
1191
- ?required="${config.required}"
1192
- .errors="${errors}"
1193
- .value="${value || ''}"
1194
- .attachments="${this.formData.attachments || []}"
1195
- placeholder="${messageConfig.placeholder || ''}"
1196
- .helpText="${config.helpText || ''}"
1197
- ?autogrow="${messageConfig.autogrow}"
1198
- ?gsm="${messageConfig.gsm}"
1199
- ?disableCompletion="${messageConfig.disableCompletion}"
1200
- counter="${messageConfig.counter || ''}"
1201
- accept="${messageConfig.accept || ''}"
1202
- endpoint="${messageConfig.endpoint || ''}"
1203
- max-attachments="${messageConfig.maxAttachments || 3}"
1204
- minHeight="${messageConfig.minHeight || 60}"
1205
- @change="${(e: Event) =>
1206
- this.handleMessageEditorChange(fieldName, e)}"
1207
- ></temba-message-editor>`;
1208
- }
1209
-
1210
- default:
1211
- return html`<div>Unsupported field type: ${(config as any).type}</div>`;
1212
- }
1024
+ });
1213
1025
  }
1214
1026
 
1215
1027
  private handleGroupToggle(groupLabel: string): void {
@@ -1234,9 +1046,7 @@ export class NodeEditor extends RapidElement {
1234
1046
  }
1235
1047
 
1236
1048
  private expandGroupsWithErrors(errors: { [key: string]: string }): void {
1237
- if (!this.action) return;
1238
-
1239
- const config = ACTION_CONFIG[this.action.type];
1049
+ const config = this.getConfig();
1240
1050
  if (!config?.layout) return;
1241
1051
 
1242
1052
  const errorFields = new Set(Object.keys(errors));
@@ -1273,7 +1083,7 @@ export class NodeEditor extends RapidElement {
1273
1083
 
1274
1084
  private renderLayoutItem(
1275
1085
  item: LayoutItem,
1276
- config: ActionConfig,
1086
+ config: ActionConfig | NodeConfig,
1277
1087
  renderedFields: Set<string>
1278
1088
  ): TemplateResult {
1279
1089
  if (typeof item === 'string') {
@@ -1310,7 +1120,7 @@ export class NodeEditor extends RapidElement {
1310
1120
 
1311
1121
  private renderRow(
1312
1122
  rowConfig: RowLayoutConfig,
1313
- config: ActionConfig,
1123
+ config: ActionConfig | NodeConfig,
1314
1124
  renderedFields: Set<string>
1315
1125
  ): TemplateResult {
1316
1126
  const { items, gap = '1rem' } = rowConfig;
@@ -1347,7 +1157,7 @@ export class NodeEditor extends RapidElement {
1347
1157
 
1348
1158
  private renderGroup(
1349
1159
  groupConfig: GroupLayoutConfig,
1350
- config: ActionConfig,
1160
+ config: ActionConfig | NodeConfig,
1351
1161
  renderedFields: Set<string>
1352
1162
  ): TemplateResult {
1353
1163
  const {
@@ -1434,7 +1244,9 @@ export class NodeEditor extends RapidElement {
1434
1244
  <div class="form-group-info">
1435
1245
  <div class="form-group-title">${label}</div>
1436
1246
  ${helpText
1437
- ? html`<div class="form-group-help">${helpText}</div>`
1247
+ ? html`<div class="form-group-help">
1248
+ ${renderMarkdownInline(helpText)}
1249
+ </div>`
1438
1250
  : ''}
1439
1251
  </div>
1440
1252
  ${groupHasErrors
@@ -1500,7 +1312,7 @@ export class NodeEditor extends RapidElement {
1500
1312
 
1501
1313
  private renderFieldRow(
1502
1314
  rowConfig: RowLayoutConfig,
1503
- config: ActionConfig
1315
+ config: ActionConfig | NodeConfig
1504
1316
  ): TemplateResult {
1505
1317
  // This method is deprecated - use renderRow instead
1506
1318
  return this.renderRow(rowConfig, config, new Set());
@@ -1508,7 +1320,7 @@ export class NodeEditor extends RapidElement {
1508
1320
 
1509
1321
  private renderFieldGroup(
1510
1322
  groupConfig: GroupLayoutConfig,
1511
- config: ActionConfig
1323
+ config: ActionConfig | NodeConfig
1512
1324
  ): TemplateResult {
1513
1325
  // This method is deprecated - use renderGroup instead
1514
1326
  return this.renderGroup(groupConfig, config, new Set());
@@ -1556,13 +1368,9 @@ export class NodeEditor extends RapidElement {
1556
1368
  }
1557
1369
 
1558
1370
  private renderFields(): TemplateResult {
1559
- if (!this.action) {
1560
- return html` <div>No action selected</div> `;
1561
- }
1562
-
1563
- const config = ACTION_CONFIG[this.action.type];
1371
+ const config = this.getConfig();
1564
1372
  if (!config) {
1565
- return html` <div>No configuration available for this action</div> `;
1373
+ return html` <div>No configuration available</div> `;
1566
1374
  }
1567
1375
 
1568
1376
  // Use the new fields configuration system
@@ -1603,7 +1411,12 @@ export class NodeEditor extends RapidElement {
1603
1411
  }
1604
1412
  }
1605
1413
 
1606
- return html` <div>No form configuration available</div> `;
1414
+ // Fallback for configs without form configuration
1415
+ if (this.action) {
1416
+ return html` <div>No form configuration available for this action</div> `;
1417
+ } else {
1418
+ return html` <div>No form configuration available for this node</div> `;
1419
+ }
1607
1420
  }
1608
1421
 
1609
1422
  private renderActionSection(): TemplateResult {
@@ -1676,12 +1489,11 @@ export class NodeEditor extends RapidElement {
1676
1489
  }
1677
1490
 
1678
1491
  const headerColor = this.getHeaderColor();
1679
- const nodeConfig = this.getNodeConfig();
1680
- const actionConfig = ACTION_CONFIG[this.action?.type];
1492
+ const config = this.getConfig();
1681
1493
 
1682
1494
  return html`
1683
1495
  <temba-dialog
1684
- header="${actionConfig?.name || nodeConfig?.name || 'Edit'}"
1496
+ header="${config?.name || 'Edit'}"
1685
1497
  .open="${this.isOpen}"
1686
1498
  @temba-button-clicked=${this.handleDialogButtonClick}
1687
1499
  primaryButtonName="Save"
@@ -1690,7 +1502,7 @@ export class NodeEditor extends RapidElement {
1690
1502
  >
1691
1503
  <div class="node-editor-form">
1692
1504
  ${this.renderFields()}
1693
- ${nodeConfig?.router?.configurable
1505
+ ${this.getNodeConfig()?.router?.configurable
1694
1506
  ? this.renderRouterSection()
1695
1507
  : null}
1696
1508
  </div>