@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
@@ -0,0 +1,466 @@
1
+ import { html, TemplateResult } from 'lit';
2
+ import {
3
+ FieldConfig,
4
+ TextFieldConfig,
5
+ TextareaFieldConfig,
6
+ SelectFieldConfig,
7
+ CheckboxFieldConfig,
8
+ MessageEditorFieldConfig,
9
+ KeyValueFieldConfig,
10
+ ArrayFieldConfig
11
+ } from '../flow/types';
12
+
13
+ /**
14
+ * FieldRenderer provides a consistent way to render field configurations
15
+ * into web components across different contexts (NodeEditor, ArrayEditor, etc.)
16
+ */
17
+ export class FieldRenderer {
18
+ /**
19
+ * Renders a field based on its configuration
20
+ * @param fieldName - The name of the field
21
+ * @param config - The field configuration
22
+ * @param value - The current value of the field
23
+ * @param context - Additional context for rendering
24
+ * @returns A TemplateResult for the rendered field
25
+ */
26
+ static renderField(
27
+ fieldName: string,
28
+ config: FieldConfig,
29
+ value: any,
30
+ context: FieldRenderContext = {}
31
+ ): TemplateResult {
32
+ /*const {
33
+ errors = [],
34
+ onChange,
35
+ showLabel = true,
36
+ flavor,
37
+ extraClasses = '',
38
+ style = ''
39
+ } = context;*/
40
+ switch (config.type) {
41
+ case 'text':
42
+ return FieldRenderer.renderTextInput(fieldName, config, value, context);
43
+
44
+ case 'textarea':
45
+ return FieldRenderer.renderTextarea(
46
+ fieldName,
47
+ config as TextareaFieldConfig,
48
+ value,
49
+ context
50
+ );
51
+
52
+ case 'select':
53
+ return FieldRenderer.renderSelect(
54
+ fieldName,
55
+ config as SelectFieldConfig,
56
+ value,
57
+ context
58
+ );
59
+
60
+ case 'checkbox':
61
+ return FieldRenderer.renderCheckbox(
62
+ fieldName,
63
+ config as CheckboxFieldConfig,
64
+ value,
65
+ context
66
+ );
67
+
68
+ case 'key-value':
69
+ return FieldRenderer.renderKeyValue(
70
+ fieldName,
71
+ config as KeyValueFieldConfig,
72
+ value,
73
+ context
74
+ );
75
+
76
+ case 'array':
77
+ return FieldRenderer.renderArray(
78
+ fieldName,
79
+ config as ArrayFieldConfig,
80
+ value,
81
+ context
82
+ );
83
+
84
+ case 'message-editor':
85
+ return FieldRenderer.renderMessageEditor(
86
+ fieldName,
87
+ config as MessageEditorFieldConfig,
88
+ value,
89
+ context
90
+ );
91
+
92
+ default:
93
+ return html`<div>Unsupported field type: ${(config as any).type}</div>`;
94
+ }
95
+ }
96
+
97
+ private static renderTextInput(
98
+ fieldName: string,
99
+ config: TextFieldConfig,
100
+ value: any,
101
+ context: FieldRenderContext
102
+ ): TemplateResult {
103
+ const {
104
+ errors = [],
105
+ onChange,
106
+ showLabel = true,
107
+ extraClasses,
108
+ style
109
+ } = context;
110
+
111
+ // If field supports expression evaluation, use temba-completion
112
+ if (config.evaluated) {
113
+ return html`<temba-completion
114
+ name="${fieldName}"
115
+ label="${showLabel ? config.label : ''}"
116
+ ?required="${config.required}"
117
+ .errors="${errors}"
118
+ .value="${value || ''}"
119
+ placeholder="${config.placeholder || ''}"
120
+ expressions="session"
121
+ .helpText="${config.helpText || ''}"
122
+ class="${extraClasses}"
123
+ style="${style}"
124
+ @input="${onChange || (() => {})}"
125
+ ></temba-completion>`;
126
+ }
127
+
128
+ return html`<temba-textinput
129
+ name="${fieldName}"
130
+ label="${showLabel ? config.label : ''}"
131
+ ?required="${config.required}"
132
+ .errors="${errors}"
133
+ .value="${value || ''}"
134
+ placeholder="${config.placeholder || ''}"
135
+ .helpText="${config.helpText || ''}"
136
+ class="${extraClasses}"
137
+ style="${style}"
138
+ @input="${onChange || (() => {})}"
139
+ ></temba-textinput>`;
140
+ }
141
+
142
+ private static renderTextarea(
143
+ fieldName: string,
144
+ config: TextareaFieldConfig,
145
+ value: any,
146
+ context: FieldRenderContext
147
+ ): TemplateResult {
148
+ const {
149
+ errors = [],
150
+ onChange,
151
+ showLabel = true,
152
+ extraClasses,
153
+ style
154
+ } = context;
155
+
156
+ const minHeightStyle = config.minHeight
157
+ ? `--textarea-min-height: ${config.minHeight}px;`
158
+ : '';
159
+ const combinedStyle = `${minHeightStyle}${style}`;
160
+
161
+ // If field supports expression evaluation, use temba-completion
162
+ if (config.evaluated) {
163
+ return html`<temba-completion
164
+ name="${fieldName}"
165
+ label="${showLabel ? config.label : ''}"
166
+ ?required="${config.required}"
167
+ .errors="${errors}"
168
+ .value="${value || ''}"
169
+ placeholder="${config.placeholder || ''}"
170
+ textarea
171
+ expressions="session"
172
+ .helpText="${config.helpText || ''}"
173
+ class="${extraClasses}"
174
+ style="${combinedStyle}"
175
+ @input="${onChange || (() => {})}"
176
+ ></temba-completion>`;
177
+ }
178
+
179
+ return html`<temba-textinput
180
+ name="${fieldName}"
181
+ label="${showLabel ? config.label : ''}"
182
+ ?required="${config.required}"
183
+ .errors="${errors}"
184
+ .value="${value || ''}"
185
+ placeholder="${config.placeholder || ''}"
186
+ textarea
187
+ .rows="${config.rows || 3}"
188
+ .helpText="${config.helpText || ''}"
189
+ class="${extraClasses}"
190
+ style="${combinedStyle}"
191
+ @input="${onChange || (() => {})}"
192
+ ></temba-textinput>`;
193
+ }
194
+
195
+ private static renderSelect(
196
+ fieldName: string,
197
+ config: SelectFieldConfig,
198
+ value: any,
199
+ context: FieldRenderContext
200
+ ): TemplateResult {
201
+ const {
202
+ errors = [],
203
+ onChange,
204
+ showLabel = true,
205
+ flavor,
206
+ extraClasses,
207
+ style
208
+ } = context;
209
+
210
+ // Ensure proper value handling for multi vs single select
211
+ const normalizedValue = (() => {
212
+ if (config.multi) {
213
+ // Multi-select: ensure we have an array and convert strings to option objects
214
+ const valueArray = Array.isArray(value) ? value : value ? [value] : [];
215
+ return valueArray.map((val) => {
216
+ if (typeof val === 'string') {
217
+ // Convert string values to option objects
218
+ return { name: val, value: val };
219
+ }
220
+ return val;
221
+ });
222
+ } else {
223
+ // Single select: use the value as-is
224
+ return value || '';
225
+ }
226
+ })();
227
+
228
+ if (typeof normalizedValue === 'string') {
229
+ return html`<temba-select
230
+ name="${fieldName}"
231
+ ?required="${config.required}"
232
+ .errors="${errors}"
233
+ value="${config.multi ? '' : normalizedValue}"
234
+ .values="${config.multi ? normalizedValue : undefined}"
235
+ ?multi="${config.multi}"
236
+ ?searchable="${config.searchable}"
237
+ ?tags="${config.tags}"
238
+ ?emails="${config.emails}"
239
+ ?clearable="${config.clearable || false}"
240
+ label="${showLabel ? config.label : ''}"
241
+ placeholder="${config.placeholder || ''}"
242
+ maxItems="${config.maxItems || 0}"
243
+ valueKey="${config.valueKey || 'value'}"
244
+ nameKey="${config.nameKey || 'name'}"
245
+ endpoint="${config.endpoint || ''}"
246
+ .helpText="${config.helpText || ''}"
247
+ flavor="${flavor || config.flavor || 'small'}"
248
+ class="${extraClasses}"
249
+ style="${style}"
250
+ .getName=${config.getName}
251
+ .createArbitraryOption=${config.createArbitraryOption}
252
+ ?allowCreate="${config.allowCreate || false}"
253
+ @change="${onChange || (() => {})}"
254
+ >
255
+ ${config.options?.map((option: any) => {
256
+ if (typeof option === 'string') {
257
+ return html`<temba-option
258
+ name="${option}"
259
+ value="${option}"
260
+ ></temba-option>`;
261
+ } else {
262
+ return html`<temba-option
263
+ name="${option.label || option.name}"
264
+ value="${option.value}"
265
+ ></temba-option>`;
266
+ }
267
+ })}
268
+ </temba-select>`;
269
+ }
270
+
271
+ return html`<temba-select
272
+ name="${fieldName}"
273
+ label="${showLabel ? config.label : ''}"
274
+ ?required="${config.required}"
275
+ .errors="${errors}"
276
+ .values="${normalizedValue}"
277
+ ?multi="${config.multi}"
278
+ ?searchable="${config.searchable}"
279
+ ?tags="${config.tags}"
280
+ ?emails="${config.emails}"
281
+ ?clearable="${config.clearable || false}"
282
+ placeholder="${config.placeholder || ''}"
283
+ maxItems="${config.maxItems || 0}"
284
+ valueKey="${config.valueKey || 'value'}"
285
+ nameKey="${config.nameKey || 'name'}"
286
+ endpoint="${config.endpoint || ''}"
287
+ .helpText="${config.helpText || ''}"
288
+ flavor="${flavor || config.flavor || 'small'}"
289
+ class="${extraClasses}"
290
+ style="${style}"
291
+ .getName=${config.getName}
292
+ .createArbitraryOption=${config.createArbitraryOption}
293
+ ?allowCreate="${config.allowCreate || false}"
294
+ @change="${onChange || (() => {})}"
295
+ >
296
+ ${config.options?.map((option: any) => {
297
+ if (typeof option === 'string') {
298
+ return html`<temba-option
299
+ name="${option}"
300
+ value="${option}"
301
+ ></temba-option>`;
302
+ } else {
303
+ return html`<temba-option
304
+ name="${option.label || option.name}"
305
+ value="${option.value}"
306
+ ></temba-option>`;
307
+ }
308
+ })}
309
+ </temba-select>`;
310
+ }
311
+
312
+ private static renderCheckbox(
313
+ fieldName: string,
314
+ config: CheckboxFieldConfig,
315
+ value: any,
316
+ context: FieldRenderContext
317
+ ): TemplateResult {
318
+ const { errors = [], onChange, extraClasses, style } = context;
319
+
320
+ return html`<div class="form-field">
321
+ <temba-checkbox
322
+ name="${fieldName}"
323
+ label="${config.label}"
324
+ .helpText="${config.helpText || ''}"
325
+ ?required="${config.required}"
326
+ .errors="${errors}"
327
+ ?checked="${value || false}"
328
+ size="${config.size || 1.2}"
329
+ animateChange="${config.animateChange || 'pulse'}"
330
+ class="${extraClasses}"
331
+ style="${style}"
332
+ @change="${onChange || (() => {})}"
333
+ ></temba-checkbox>
334
+ ${errors.length
335
+ ? html`<div class="field-errors">${errors.join(', ')}</div>`
336
+ : ''}
337
+ </div>`;
338
+ }
339
+
340
+ private static renderKeyValue(
341
+ fieldName: string,
342
+ config: KeyValueFieldConfig,
343
+ value: any,
344
+ context: FieldRenderContext
345
+ ): TemplateResult {
346
+ const {
347
+ errors = [],
348
+ onChange,
349
+ showLabel = true,
350
+ extraClasses,
351
+ style
352
+ } = context;
353
+
354
+ return html`<div class="form-field">
355
+ ${showLabel ? html`<label>${config.label}</label>` : ''}
356
+ <temba-key-value-editor
357
+ name="${fieldName}"
358
+ .value="${value || []}"
359
+ .sortable="${config.sortable}"
360
+ .keyPlaceholder="${config.keyPlaceholder || 'Key'}"
361
+ .valuePlaceholder="${config.valuePlaceholder || 'Value'}"
362
+ .minRows="${config.minRows || 0}"
363
+ class="${extraClasses}"
364
+ style="${style}"
365
+ @change="${onChange || (() => {})}"
366
+ ></temba-key-value-editor>
367
+ ${errors.length
368
+ ? html`<div class="field-errors">${errors.join(', ')}</div>`
369
+ : ''}
370
+ </div>`;
371
+ }
372
+
373
+ private static renderArray(
374
+ fieldName: string,
375
+ config: ArrayFieldConfig,
376
+ value: any,
377
+ context: FieldRenderContext
378
+ ): TemplateResult {
379
+ const {
380
+ errors = [],
381
+ onChange,
382
+ showLabel = true,
383
+ extraClasses,
384
+ style
385
+ } = context;
386
+
387
+ return html`<div class="form-field">
388
+ <temba-array-editor
389
+ name="${fieldName}"
390
+ .label="${showLabel ? config.label : ''}"
391
+ .value="${value || []}"
392
+ .itemConfig="${config.itemConfig}"
393
+ .sortable="${config.sortable}"
394
+ .itemLabel="${config.itemLabel || 'Item'}"
395
+ .minItems="${config.minItems || 0}"
396
+ .maxItems="${config.maxItems || 0}"
397
+ .onItemChange="${config.onItemChange}"
398
+ .isEmptyItemFn="${config.isEmptyItem}"
399
+ class="${extraClasses}"
400
+ style="${style}"
401
+ @change="${onChange || (() => {})}"
402
+ ></temba-array-editor>
403
+ ${errors.length
404
+ ? html`<div class="field-errors">${errors.join(', ')}</div>`
405
+ : ''}
406
+ </div>`;
407
+ }
408
+
409
+ private static renderMessageEditor(
410
+ fieldName: string,
411
+ config: MessageEditorFieldConfig,
412
+ value: any,
413
+ context: FieldRenderContext
414
+ ): TemplateResult {
415
+ const {
416
+ errors = [],
417
+ onChange,
418
+ showLabel = true,
419
+ extraClasses,
420
+ style,
421
+ additionalData = {}
422
+ } = context;
423
+
424
+ return html`<temba-message-editor
425
+ name="${fieldName}"
426
+ label="${showLabel ? config.label : ''}"
427
+ ?required="${config.required}"
428
+ .errors="${errors}"
429
+ .value="${value || ''}"
430
+ .attachments="${additionalData.attachments || []}"
431
+ placeholder="${config.placeholder || ''}"
432
+ .helpText="${config.helpText || ''}"
433
+ ?autogrow="${config.autogrow}"
434
+ ?gsm="${config.gsm}"
435
+ ?disableCompletion="${config.disableCompletion}"
436
+ counter="${config.counter || ''}"
437
+ accept="${config.accept || ''}"
438
+ endpoint="${config.endpoint || ''}"
439
+ max-attachments="${config.maxAttachments || 3}"
440
+ minHeight="${config.minHeight || 60}"
441
+ class="${extraClasses}"
442
+ style="${style}"
443
+ @change="${onChange || (() => {})}"
444
+ ></temba-message-editor>`;
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Context object for field rendering that provides additional options
450
+ */
451
+ export interface FieldRenderContext {
452
+ /** Array of error messages for the field */
453
+ errors?: string[];
454
+ /** Change event handler */
455
+ onChange?: (event: Event) => void;
456
+ /** Whether to show the field label */
457
+ showLabel?: boolean;
458
+ /** Flavor for components that support it (like temba-select) */
459
+ flavor?: string;
460
+ /** Additional CSS classes to apply */
461
+ extraClasses?: string;
462
+ /** Additional CSS styles to apply */
463
+ style?: string;
464
+ /** Additional data needed for specific field types */
465
+ additionalData?: Record<string, any>;
466
+ }