@nyaruka/temba-components 0.129.9 → 0.129.11

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 (323) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/demo/data/flows/sample-flow.json +96 -56
  3. package/demo/test-colorpicker.html +30 -0
  4. package/dist/temba-components.js +896 -934
  5. package/dist/temba-components.js.map +1 -1
  6. package/out-tsc/src/events.js.map +1 -1
  7. package/out-tsc/src/flow/Editor.js +9 -6
  8. package/out-tsc/src/flow/Editor.js.map +1 -1
  9. package/out-tsc/src/flow/actions/set_contact_channel.js +1 -1
  10. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  11. package/out-tsc/src/flow/actions/set_contact_field.js +1 -1
  12. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  13. package/out-tsc/src/flow/actions/set_contact_language.js +1 -1
  14. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  15. package/out-tsc/src/flow/actions/set_contact_status.js +1 -1
  16. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  17. package/out-tsc/src/flow/config.js +2 -8
  18. package/out-tsc/src/flow/config.js.map +1 -1
  19. package/out-tsc/src/flow/nodes/split_by_llm.js +101 -0
  20. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -0
  21. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +4 -89
  22. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  23. package/out-tsc/src/flow/nodes/split_by_subflow.js +123 -3
  24. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
  25. package/out-tsc/src/flow/nodes/split_by_ticket.js +115 -13
  26. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
  27. package/out-tsc/src/flow/nodes/split_by_webhook.js +160 -12
  28. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
  29. package/out-tsc/src/form/ArrayEditor.js +45 -56
  30. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  31. package/out-tsc/src/form/BaseListEditor.js +4 -3
  32. package/out-tsc/src/form/BaseListEditor.js.map +1 -1
  33. package/out-tsc/src/form/Checkbox.js +77 -24
  34. package/out-tsc/src/form/Checkbox.js.map +1 -1
  35. package/out-tsc/src/form/ColorPicker.js +28 -40
  36. package/out-tsc/src/form/ColorPicker.js.map +1 -1
  37. package/out-tsc/src/form/Completion.js +44 -53
  38. package/out-tsc/src/form/Completion.js.map +1 -1
  39. package/out-tsc/src/form/Compose.js +7 -8
  40. package/out-tsc/src/form/Compose.js.map +1 -1
  41. package/out-tsc/src/form/ContactSearch.js +3 -4
  42. package/out-tsc/src/form/ContactSearch.js.map +1 -1
  43. package/out-tsc/src/form/DatePicker.js +29 -36
  44. package/out-tsc/src/form/DatePicker.js.map +1 -1
  45. package/out-tsc/src/form/{FormField.js → FieldElement.js} +78 -50
  46. package/out-tsc/src/form/FieldElement.js.map +1 -0
  47. package/out-tsc/src/form/FieldRenderer.js +2 -1
  48. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  49. package/out-tsc/src/form/ImagePicker.js +122 -126
  50. package/out-tsc/src/form/ImagePicker.js.map +1 -1
  51. package/out-tsc/src/form/KeyValueEditor.js +41 -37
  52. package/out-tsc/src/form/KeyValueEditor.js.map +1 -1
  53. package/out-tsc/src/form/MessageEditor.js +55 -63
  54. package/out-tsc/src/form/MessageEditor.js.map +1 -1
  55. package/out-tsc/src/form/TembaSlider.js +3 -3
  56. package/out-tsc/src/form/TembaSlider.js.map +1 -1
  57. package/out-tsc/src/form/TemplateEditor.js +3 -3
  58. package/out-tsc/src/form/TemplateEditor.js.map +1 -1
  59. package/out-tsc/src/form/TextInput.js +22 -26
  60. package/out-tsc/src/form/TextInput.js.map +1 -1
  61. package/out-tsc/src/form/select/Select.js +9 -15
  62. package/out-tsc/src/form/select/Select.js.map +1 -1
  63. package/out-tsc/src/form/select/UserSelect.js +8 -9
  64. package/out-tsc/src/form/select/UserSelect.js.map +1 -1
  65. package/out-tsc/src/form/select/WorkspaceSelect.js +7 -8
  66. package/out-tsc/src/form/select/WorkspaceSelect.js.map +1 -1
  67. package/out-tsc/src/live/ContactChat.js +73 -99
  68. package/out-tsc/src/live/ContactChat.js.map +1 -1
  69. package/out-tsc/src/live/ContactFieldEditor.js.map +1 -1
  70. package/out-tsc/src/utils.js +115 -0
  71. package/out-tsc/src/utils.js.map +1 -1
  72. package/out-tsc/temba-modules.js +3 -2
  73. package/out-tsc/temba-modules.js.map +1 -1
  74. package/out-tsc/test/nodes/split_by_llm.test.js +174 -0
  75. package/out-tsc/test/nodes/split_by_llm.test.js.map +1 -0
  76. package/out-tsc/test/temba-checkbox.test.js +16 -0
  77. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  78. package/out-tsc/test/temba-integration-markdown.test.js +2 -4
  79. package/out-tsc/test/temba-integration-markdown.test.js.map +1 -1
  80. package/out-tsc/test/temba-slider.test.js +0 -1
  81. package/out-tsc/test/temba-slider.test.js.map +1 -1
  82. package/package.json +1 -1
  83. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  84. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  85. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  86. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  87. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  88. package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
  89. package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
  90. package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
  91. package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
  92. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  93. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  94. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  95. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  96. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  97. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  98. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  99. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  100. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  101. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  102. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  103. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  104. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  105. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  106. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  107. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  108. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  109. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  110. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  111. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  112. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  113. package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
  114. package/screenshots/truth/checkbox/checkbox-no-label-no-background-hover.png +0 -0
  115. package/screenshots/truth/checkbox/checkbox-whitespace-label-no-background-hover.png +0 -0
  116. package/screenshots/truth/checkbox/checkbox-with-help-text.png +0 -0
  117. package/screenshots/truth/checkbox/checked.png +0 -0
  118. package/screenshots/truth/checkbox/default.png +0 -0
  119. package/screenshots/truth/colorpicker/default.png +0 -0
  120. package/screenshots/truth/colorpicker/focused.png +0 -0
  121. package/screenshots/truth/colorpicker/initialized.png +0 -0
  122. package/screenshots/truth/colorpicker/selected.png +0 -0
  123. package/screenshots/truth/compose/attachments-tab.png +0 -0
  124. package/screenshots/truth/compose/attachments-with-files.png +0 -0
  125. package/screenshots/truth/compose/intial-text.png +0 -0
  126. package/screenshots/truth/compose/no-counter.png +0 -0
  127. package/screenshots/truth/compose/wraps-text-and-spaces.png +0 -0
  128. package/screenshots/truth/compose/wraps-text-and-url.png +0 -0
  129. package/screenshots/truth/compose/wraps-text-no-spaces.png +0 -0
  130. package/screenshots/truth/contacts/chat-failure.png +0 -0
  131. package/screenshots/truth/contacts/chat-for-active-contact.png +0 -0
  132. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  133. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  134. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  135. package/screenshots/truth/counter/summary.png +0 -0
  136. package/screenshots/truth/counter/text.png +0 -0
  137. package/screenshots/truth/counter/unicode-variables.png +0 -0
  138. package/screenshots/truth/counter/unicode.png +0 -0
  139. package/screenshots/truth/counter/variable.png +0 -0
  140. package/screenshots/truth/datepicker/date-truncated-time.png +0 -0
  141. package/screenshots/truth/datepicker/date.png +0 -0
  142. package/screenshots/truth/datepicker/initial-timezone.png +0 -0
  143. package/screenshots/truth/datepicker/range-picker-editing-start.png +0 -0
  144. package/screenshots/truth/datepicker/updated-keyboard-date.png +0 -0
  145. package/screenshots/truth/dialog/focused.png +0 -0
  146. package/screenshots/truth/dropdown/right-edge-collision.png +0 -0
  147. package/screenshots/truth/editor/router.png +0 -0
  148. package/screenshots/truth/editor/send_msg.png +0 -0
  149. package/screenshots/truth/editor/set_contact_language.png +0 -0
  150. package/screenshots/truth/editor/set_contact_name.png +0 -0
  151. package/screenshots/truth/editor/set_run_result.png +0 -0
  152. package/screenshots/truth/editor/wait.png +0 -0
  153. package/screenshots/truth/field-renderer/checkbox-checked.png +0 -0
  154. package/screenshots/truth/field-renderer/checkbox-unchecked.png +0 -0
  155. package/screenshots/truth/field-renderer/checkbox-with-errors.png +0 -0
  156. package/screenshots/truth/field-renderer/context-comparison.png +0 -0
  157. package/screenshots/truth/field-renderer/key-value-with-label.png +0 -0
  158. package/screenshots/truth/field-renderer/message-editor-with-label.png +0 -0
  159. package/screenshots/truth/field-renderer/select-multi.png +0 -0
  160. package/screenshots/truth/field-renderer/select-no-label.png +0 -0
  161. package/screenshots/truth/field-renderer/select-with-label.png +0 -0
  162. package/screenshots/truth/field-renderer/text-evaluated.png +0 -0
  163. package/screenshots/truth/field-renderer/text-no-label.png +0 -0
  164. package/screenshots/truth/field-renderer/text-with-errors.png +0 -0
  165. package/screenshots/truth/field-renderer/text-with-label.png +0 -0
  166. package/screenshots/truth/field-renderer/textarea-evaluated.png +0 -0
  167. package/screenshots/truth/field-renderer/textarea-with-label.png +0 -0
  168. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
  169. package/screenshots/truth/list/fields-dragging.png +0 -0
  170. package/screenshots/truth/list/fields-filtered.png +0 -0
  171. package/screenshots/truth/list/fields-hovered.png +0 -0
  172. package/screenshots/truth/list/fields.png +0 -0
  173. package/screenshots/truth/list/items-selected.png +0 -0
  174. package/screenshots/truth/list/items-updated.png +0 -0
  175. package/screenshots/truth/list/items.png +0 -0
  176. package/screenshots/truth/menu/menu-focused-with items.png +0 -0
  177. package/screenshots/truth/menu/menu-refresh-1.png +0 -0
  178. package/screenshots/truth/menu/menu-refresh-2.png +0 -0
  179. package/screenshots/truth/menu/menu-submenu.png +0 -0
  180. package/screenshots/truth/menu/menu-tasks-nextup.png +0 -0
  181. package/screenshots/truth/menu/menu-tasks.png +0 -0
  182. package/screenshots/truth/message-editor/autogrow-initial-content.png +0 -0
  183. package/screenshots/truth/message-editor/default.png +0 -0
  184. package/screenshots/truth/message-editor/drag-highlight.png +0 -0
  185. package/screenshots/truth/message-editor/filtered-attachments.png +0 -0
  186. package/screenshots/truth/message-editor/with-completion.png +0 -0
  187. package/screenshots/truth/message-editor/with-properties.png +0 -0
  188. package/screenshots/truth/modax/form.png +0 -0
  189. package/screenshots/truth/modax/simple.png +0 -0
  190. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  191. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  192. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  193. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  194. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  195. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  196. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  197. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  198. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  199. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  200. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  201. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  202. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  203. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  204. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  205. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  206. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  207. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  208. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  209. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  210. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  211. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  212. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  213. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  214. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  215. package/screenshots/truth/omnibox/selected.png +0 -0
  216. package/screenshots/truth/options/block.png +0 -0
  217. package/screenshots/truth/run-list/basic.png +0 -0
  218. package/screenshots/truth/select/disabled-multi-selection.png +0 -0
  219. package/screenshots/truth/select/disabled-selection.png +0 -0
  220. package/screenshots/truth/select/disabled.png +0 -0
  221. package/screenshots/truth/select/embedded.png +0 -0
  222. package/screenshots/truth/select/empty-options.png +0 -0
  223. package/screenshots/truth/select/expression-selected.png +0 -0
  224. package/screenshots/truth/select/expressions.png +0 -0
  225. package/screenshots/truth/select/functions.png +0 -0
  226. package/screenshots/truth/select/local-options.png +0 -0
  227. package/screenshots/truth/select/multi-with-endpoint.png +0 -0
  228. package/screenshots/truth/select/multiple-initial-values.png +0 -0
  229. package/screenshots/truth/select/remote-options.png +0 -0
  230. package/screenshots/truth/select/search-enabled.png +0 -0
  231. package/screenshots/truth/select/search-multi-no-matches.png +0 -0
  232. package/screenshots/truth/select/search-selected-focus.png +0 -0
  233. package/screenshots/truth/select/search-selected.png +0 -0
  234. package/screenshots/truth/select/search-with-selected.png +0 -0
  235. package/screenshots/truth/select/searching.png +0 -0
  236. package/screenshots/truth/select/selected-multi-maxitems-reached.png +0 -0
  237. package/screenshots/truth/select/selected-multi.png +0 -0
  238. package/screenshots/truth/select/selected-single.png +0 -0
  239. package/screenshots/truth/select/selection-clearable.png +0 -0
  240. package/screenshots/truth/select/static-initial-value.png +0 -0
  241. package/screenshots/truth/select/static-initial-via-selected.png +0 -0
  242. package/screenshots/truth/select/truncated-selection.png +0 -0
  243. package/screenshots/truth/select/with-placeholder.png +0 -0
  244. package/screenshots/truth/select/without-placeholder.png +0 -0
  245. package/screenshots/truth/slider/update-slider-on-circle-dragged.png +0 -0
  246. package/screenshots/truth/templates/default.png +0 -0
  247. package/screenshots/truth/templates/unapproved.png +0 -0
  248. package/screenshots/truth/textinput/autogrow-initial.png +0 -0
  249. package/screenshots/truth/textinput/input-disabled.png +0 -0
  250. package/screenshots/truth/textinput/input-focused.png +0 -0
  251. package/screenshots/truth/textinput/input-form.png +0 -0
  252. package/screenshots/truth/textinput/input-inserted.png +0 -0
  253. package/screenshots/truth/textinput/input-placeholder.png +0 -0
  254. package/screenshots/truth/textinput/input-updated.png +0 -0
  255. package/screenshots/truth/textinput/input.png +0 -0
  256. package/screenshots/truth/textinput/textarea-focused.png +0 -0
  257. package/screenshots/truth/textinput/textarea.png +0 -0
  258. package/src/events.ts +9 -8
  259. package/src/flow/Editor.ts +6 -3
  260. package/src/flow/actions/set_contact_channel.ts +1 -1
  261. package/src/flow/actions/set_contact_field.ts +1 -1
  262. package/src/flow/actions/set_contact_language.ts +1 -1
  263. package/src/flow/actions/set_contact_status.ts +1 -1
  264. package/src/flow/config.ts +2 -8
  265. package/src/flow/nodes/split_by_llm.ts +119 -0
  266. package/src/flow/nodes/split_by_llm_categorize.ts +13 -116
  267. package/src/flow/nodes/split_by_subflow.ts +153 -3
  268. package/src/flow/nodes/split_by_ticket.ts +135 -12
  269. package/src/flow/nodes/split_by_webhook.ts +187 -12
  270. package/src/form/ArrayEditor.ts +45 -57
  271. package/src/form/BaseListEditor.ts +4 -4
  272. package/src/form/Checkbox.ts +81 -24
  273. package/src/form/ColorPicker.ts +31 -43
  274. package/src/form/Completion.ts +49 -56
  275. package/src/form/Compose.ts +8 -8
  276. package/src/form/ContactSearch.ts +3 -4
  277. package/src/form/DatePicker.ts +32 -38
  278. package/src/form/{FormField.ts → FieldElement.ts} +105 -52
  279. package/src/form/FieldRenderer.ts +2 -1
  280. package/src/form/ImagePicker.ts +107 -110
  281. package/src/form/KeyValueEditor.ts +43 -39
  282. package/src/form/MessageEditor.ts +61 -67
  283. package/src/form/TembaSlider.ts +3 -3
  284. package/src/form/TemplateEditor.ts +3 -3
  285. package/src/form/TextInput.ts +25 -28
  286. package/src/form/select/Select.ts +12 -17
  287. package/src/form/select/UserSelect.ts +10 -11
  288. package/src/form/select/WorkspaceSelect.ts +9 -10
  289. package/src/live/ContactChat.ts +81 -92
  290. package/src/live/ContactFieldEditor.ts +2 -2
  291. package/src/store/flow-definition.d.ts +2 -1
  292. package/src/utils.ts +192 -0
  293. package/temba-modules.ts +3 -2
  294. package/test/nodes/split_by_llm.test.ts +232 -0
  295. package/test/temba-checkbox.test.ts +26 -0
  296. package/test/temba-integration-markdown.test.ts +2 -4
  297. package/test/temba-slider.test.ts +0 -1
  298. package/test-assets/contacts/history.json +7 -20
  299. package/test-assets/style.css +36 -234
  300. package/web-dev-server.config.mjs +26 -0
  301. package/web-test-runner.config.mjs +1 -1
  302. package/out-tsc/src/flow/actions/call_llm.js +0 -64
  303. package/out-tsc/src/flow/actions/call_llm.js.map +0 -1
  304. package/out-tsc/src/flow/actions/call_webhook.js +0 -131
  305. package/out-tsc/src/flow/actions/call_webhook.js.map +0 -1
  306. package/out-tsc/src/flow/actions/enter_flow.js +0 -14
  307. package/out-tsc/src/flow/actions/enter_flow.js.map +0 -1
  308. package/out-tsc/src/flow/actions/open_ticket.js +0 -73
  309. package/out-tsc/src/flow/actions/open_ticket.js.map +0 -1
  310. package/out-tsc/src/form/FormElement.js +0 -67
  311. package/out-tsc/src/form/FormElement.js.map +0 -1
  312. package/out-tsc/src/form/FormField.js.map +0 -1
  313. package/out-tsc/test/actions/call_llm.test.js +0 -103
  314. package/out-tsc/test/actions/call_llm.test.js.map +0 -1
  315. package/out-tsc/test/temba-formfield.test.js +0 -94
  316. package/out-tsc/test/temba-formfield.test.js.map +0 -1
  317. package/src/flow/actions/call_llm.ts +0 -66
  318. package/src/flow/actions/call_webhook.ts +0 -143
  319. package/src/flow/actions/enter_flow.ts +0 -15
  320. package/src/flow/actions/open_ticket.ts +0 -83
  321. package/src/form/FormElement.ts +0 -69
  322. package/test/actions/call_llm.test.ts +0 -137
  323. package/test/temba-formfield.test.ts +0 -121
@@ -126,7 +126,7 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
126
126
  return currentValue;
127
127
  }
128
128
 
129
- private renderField(
129
+ private renderArrayField(
130
130
  itemIndex: number,
131
131
  fieldName: string,
132
132
  config: FieldConfig
@@ -180,7 +180,7 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
180
180
  ${Object.entries(this.itemConfig).map(
181
181
  ([fieldName, config]) => html`
182
182
  <div class="field">
183
- ${this.renderField(index, fieldName, config)}
183
+ ${this.renderArrayField(index, fieldName, config)}
184
184
  </div>
185
185
  `
186
186
  )}
@@ -203,68 +203,56 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
203
203
  return 'array-editor';
204
204
  }
205
205
 
206
- protected renderAddButton(): TemplateResult {
207
- return html`
208
- <button class="add-btn" @click=${() => this.addItem()}>
209
- Add ${this.itemLabel}
210
- </button>
211
- `;
212
- }
206
+ static get styles() {
207
+ return css`
208
+ ${super.styles}
213
209
 
214
- static styles = css`
215
- .array-editor {
216
- }
217
-
218
- .array-item {
219
- }
210
+ .array-editor {
211
+ }
220
212
 
221
- .item-header {
222
- display: flex;
223
- justify-content: space-between;
224
- align-items: center;
225
- }
213
+ .array-item {
214
+ }
226
215
 
227
- .item-title {
228
- font-weight: 600;
229
- color: #333;
230
- }
216
+ .item-header {
217
+ display: flex;
218
+ justify-content: space-between;
219
+ align-items: center;
220
+ }
231
221
 
232
- .item-fields {
233
- display: flex;
234
- gap: 12px;
235
- align-items: center;
236
- }
222
+ .item-title {
223
+ font-weight: 600;
224
+ color: #333;
225
+ }
237
226
 
238
- .field {
239
- flex: 1;
240
- }
227
+ .item-fields {
228
+ display: flex;
229
+ gap: 12px;
230
+ align-items: center;
231
+ }
241
232
 
242
- .field label {
243
- display: block;
244
- margin-bottom: 4px;
245
- font-weight: 500;
246
- color: #555;
247
- font-size: 14px;
248
- }
233
+ .field {
234
+ flex: 1;
235
+ }
249
236
 
250
- .add-btn,
251
- .remove-btn {
252
- padding: 8px;
253
- border: 1px solid #ccc;
254
- border-radius: 4px;
255
- background: white;
256
- cursor: pointer;
257
- font-size: 14px;
258
- }
237
+ .add-btn,
238
+ .remove-btn {
239
+ padding: 8px;
240
+ border: 1px solid #ccc;
241
+ border-radius: 4px;
242
+ background: white;
243
+ cursor: pointer;
244
+ font-size: 14px;
245
+ }
259
246
 
260
- .add-btn:hover,
261
- .remove-btn:hover {
262
- background: #f8f8f8;
263
- }
247
+ .add-btn:hover,
248
+ .remove-btn:hover {
249
+ background: #f8f8f8;
250
+ }
264
251
 
265
- .remove-btn {
266
- background: #fefefe;
267
- color: #999;
268
- }
269
- `;
252
+ .remove-btn {
253
+ background: #fefefe;
254
+ color: #999;
255
+ }
256
+ `;
257
+ }
270
258
  }
@@ -1,5 +1,6 @@
1
- import { LitElement, TemplateResult, html } from 'lit';
1
+ import { TemplateResult, html } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
+ import { FieldElement } from './FieldElement';
3
4
 
4
5
  export interface ListItem {
5
6
  [key: string]: any;
@@ -22,7 +23,7 @@ export interface ListEditorConfig {
22
23
 
23
24
  export abstract class BaseListEditor<
24
25
  T extends ListItem = ListItem
25
- > extends LitElement {
26
+ > extends FieldElement {
26
27
  @property({ attribute: false })
27
28
  protected _items: T[] = [];
28
29
 
@@ -60,9 +61,8 @@ export abstract class BaseListEditor<
60
61
  return !this.maxItems || this._items.length < this.maxItems;
61
62
  }
62
63
 
63
- render(): TemplateResult {
64
+ renderWidget(): TemplateResult {
64
65
  const items = this.displayItems;
65
-
66
66
  return html`
67
67
  <div class=${this.getContainerClass()}>
68
68
  <div
@@ -1,11 +1,14 @@
1
1
  import { TemplateResult, html, css } from 'lit';
2
- import { FormElement } from './FormElement';
2
+ import { FieldElement } from './FieldElement';
3
3
  import { property } from 'lit/decorators.js';
4
4
  import { Icon } from '../Icons';
5
+ import { renderMarkdownInline } from '../markdown';
5
6
 
6
- export class Checkbox extends FormElement {
7
+ export class Checkbox extends FieldElement {
7
8
  static get styles() {
8
9
  return css`
10
+ ${super.styles}
11
+
9
12
  :host {
10
13
  color: var(--color-text);
11
14
  display: inline-block;
@@ -24,25 +27,45 @@ export class Checkbox extends FormElement {
24
27
  background: var(--checkbox-hover-bg, #f9f9f9);
25
28
  }
26
29
 
27
- temba-field {
28
- --help-text-margin-left: 24px;
29
- cursor: pointer;
30
- }
31
-
32
30
  .checkbox-container {
33
31
  cursor: pointer;
34
32
  display: flex;
33
+ align-items: flex-start;
35
34
  user-select: none;
36
35
  -webkit-user-select: none;
37
36
  }
38
37
 
38
+ .checkbox-container temba-icon {
39
+ align-self: flex-start;
40
+ vertical-align: top;
41
+ line-height: 1;
42
+ }
43
+
44
+ .label-and-help {
45
+ flex-grow: 1;
46
+ margin-left: 8px;
47
+ }
48
+
39
49
  .checkbox-label {
40
50
  font-family: var(--font-family);
41
51
  padding: 0px;
42
- margin-left: 8px;
52
+ margin: 0px;
43
53
  font-size: 14px;
44
54
  line-height: 19px;
45
- flex-grow: 1;
55
+ }
56
+
57
+ .checkbox-help-text {
58
+ font-family: var(--font-family);
59
+ font-size: var(--help-text-size, 0.85em);
60
+ line-height: normal;
61
+ color: var(--color-text-help);
62
+ margin-top: 4px;
63
+ opacity: 1;
64
+ }
65
+
66
+ /* Checkbox help text should align with the checkbox icon, not indented */
67
+ .help-text {
68
+ margin-left: 0;
46
69
  }
47
70
 
48
71
  .far {
@@ -123,7 +146,7 @@ export class Checkbox extends FormElement {
123
146
  super.click();
124
147
  }
125
148
 
126
- public render(): TemplateResult {
149
+ protected renderWidget(): TemplateResult {
127
150
  const icon = html`<temba-icon
128
151
  name="${this.checked
129
152
  ? Icon.checkbox_checked
@@ -132,27 +155,61 @@ export class Checkbox extends FormElement {
132
155
  : Icon.checkbox}"
133
156
  size="${this.size}"
134
157
  animatechange="${this.animateChange}"
135
- />`;
158
+ ></temba-icon>`;
136
159
 
137
160
  return html`
138
- <div class="wrapper ${this.label ? 'label' : ''}">
139
- <temba-field
140
- name=${this.name}
141
- .helpText=${this.helpText}
142
- .errors=${this.errors}
143
- .widgetOnly=${this.widgetOnly}
144
- .helpAlways=${true}
145
- ?disabled=${this.disabled}
146
- @click=${this.handleClick}
147
- >
148
- <div class="checkbox-container ${this.disabled ? 'disabled' : ''}">
149
- ${icon}
161
+ <div
162
+ class="wrapper ${this.label ? 'label' : ''}"
163
+ @click=${this.handleClick}
164
+ >
165
+ <div class="checkbox-container ${this.disabled ? 'disabled' : ''}">
166
+ ${icon}
167
+ <div class="label-and-help">
150
168
  ${this.label && String(this.label).trim()
151
169
  ? html`<div class="checkbox-label">${this.label}</div>`
152
170
  : null}
171
+ ${this.helpText && this.helpText !== 'None'
172
+ ? html` <div class="checkbox-help-text">${this.helpText}</div> `
173
+ : null}
153
174
  </div>
154
- </temba-field>
175
+ </div>
176
+ </div>
177
+ `;
178
+ }
179
+
180
+ protected renderField(): TemplateResult {
181
+ // Use standard FieldElement behavior but skip the field label since checkbox renders its own inline
182
+ const hasErrors = !this.hideErrors && this.errors && this.errors.length > 0;
183
+ const errors = hasErrors
184
+ ? this.errors.map((error: string) => {
185
+ return html`
186
+ <div class="alert-error">${renderMarkdownInline(error)}</div>
187
+ `;
188
+ })
189
+ : [];
190
+
191
+ if (this.widgetOnly) {
192
+ return html`
193
+ <div class="${this.disabled ? 'disabled' : ''}">
194
+ ${this.renderWidget()}
195
+ </div>
196
+ ${errors}
197
+ `;
198
+ }
199
+
200
+ // This matches FieldElement.renderField() but without the field label
201
+ return html`
202
+ <div
203
+ class="field ${this.disabled ? 'disabled' : ''} ${hasErrors
204
+ ? 'has-error'
205
+ : ''}"
206
+ >
207
+ <div class="widget">${this.renderWidget()} ${errors}</div>
155
208
  </div>
156
209
  `;
157
210
  }
211
+
212
+ public render(): TemplateResult {
213
+ return this.renderField();
214
+ }
158
215
  }
@@ -1,10 +1,10 @@
1
1
  import { html, css, PropertyValueMap } from 'lit';
2
- import { FormElement } from './FormElement';
2
+ import { FieldElement } from './FieldElement';
3
3
  import { property } from 'lit/decorators.js';
4
4
  import { getClasses, hslToHex } from '../utils';
5
5
  import { TextInput } from './TextInput';
6
6
 
7
- export class ColorPicker extends FormElement {
7
+ export class ColorPicker extends FieldElement {
8
8
  @property({ type: Boolean })
9
9
  expanded = false;
10
10
 
@@ -24,6 +24,8 @@ export class ColorPicker extends FormElement {
24
24
 
25
25
  static get styles() {
26
26
  return css`
27
+ ${super.styles}
28
+
27
29
  :host {
28
30
  color: var(--color-text);
29
31
  display: inline-block;
@@ -38,11 +40,6 @@ export class ColorPicker extends FormElement {
38
40
  width: 5em;
39
41
  }
40
42
 
41
- temba-field {
42
- display: block;
43
- width: 100%;
44
- }
45
-
46
43
  .wrapper {
47
44
  border: 1px solid var(--color-widget-border);
48
45
  padding: calc(var(--curvature) / 2);
@@ -221,46 +218,37 @@ export class ColorPicker extends FormElement {
221
218
  return value;
222
219
  }
223
220
 
224
- public render() {
221
+ protected renderWidget() {
225
222
  return html`
226
- <temba-field
227
- name=${this.name}
228
- .helpText=${this.helpText}
229
- .errors=${this.errors}
230
- .widgetOnly=${this.widgetOnly}
231
- .hideLabel=${this.hideLabel}
232
- .disabled=${this.disabled}
233
- >
234
- <div style="display:flex" tabindex="0">
235
- <div class=${getClasses({ wrapper: true, expanded: this.expanded })}>
236
- <div class=${getClasses({ 'picker-wrapper': true })}>
237
- <div
238
- class=${getClasses({
239
- preview: true,
240
- selecting: this.selecting
241
- })}
242
- style="color:${this.labelColor};background:${this.previewColor}"
243
- @click=${this.handlePreviewClick}
244
- >
245
- ${this.label}
246
- </div>
247
- <div
248
- class="color-picker"
249
- tabindex="0"
250
- @blur=${this.handleBlur}
251
- @mousemove=${this.handleMouseMove}
252
- @mouseout=${this.handleMouseOut}
253
- @click=${this.handleColorClick}
254
- ></div>
223
+ <div style="display:flex" tabindex="0">
224
+ <div class=${getClasses({ wrapper: true, expanded: this.expanded })}>
225
+ <div class=${getClasses({ 'picker-wrapper': true })}>
226
+ <div
227
+ class=${getClasses({
228
+ preview: true,
229
+ selecting: this.selecting
230
+ })}
231
+ style="color:${this.labelColor};background:${this.previewColor}"
232
+ @click=${this.handlePreviewClick}
233
+ >
234
+ ${this.label}
255
235
  </div>
256
- <temba-textinput
257
- value=${this.hex}
258
- @input=${this.handleHexInput}
259
- placeholder="#000000"
260
- ></temba-textinput>
236
+ <div
237
+ class="color-picker"
238
+ tabindex="0"
239
+ @blur=${this.handleBlur}
240
+ @mousemove=${this.handleMouseMove}
241
+ @mouseout=${this.handleMouseOut}
242
+ @click=${this.handleColorClick}
243
+ ></div>
261
244
  </div>
245
+ <temba-textinput
246
+ value=${this.hex}
247
+ @input=${this.handleHexInput}
248
+ placeholder="#000000"
249
+ ></temba-textinput>
262
250
  </div>
263
- </temba-field>
251
+ </div>
264
252
  `;
265
253
  }
266
254
  }
@@ -8,7 +8,7 @@ import {
8
8
  executeCompletionQuery
9
9
  } from '../excellent/helpers';
10
10
 
11
- import { FormElement } from './FormElement';
11
+ import { FieldElement } from './FieldElement';
12
12
  import { CompletionOption, Position } from '../interfaces';
13
13
  import { styleMap } from 'lit-html/directives/style-map.js';
14
14
  import { msg } from '@lit/localize';
@@ -16,9 +16,10 @@ import { msg } from '@lit/localize';
16
16
  /**
17
17
  * Completion is a text input that handles excellent completion options in a popup
18
18
  */
19
- export class Completion extends FormElement {
19
+ export class Completion extends FieldElement {
20
20
  static get styles() {
21
21
  return css`
22
+ ${super.styles}
22
23
  :host {
23
24
  display: block;
24
25
  }
@@ -69,7 +70,6 @@ export class Completion extends FormElement {
69
70
  }
70
71
 
71
72
  code {
72
- background: rgba(0, 0, 0, 0.1);
73
73
  padding: 1px 5px;
74
74
  border-radius: var(--curvature);
75
75
  }
@@ -106,9 +106,6 @@ export class Completion extends FormElement {
106
106
  @property({ type: String })
107
107
  name = '';
108
108
 
109
- @property({ type: String })
110
- value = '';
111
-
112
109
  @property({ type: Boolean })
113
110
  textarea: boolean;
114
111
 
@@ -259,7 +256,7 @@ export class Completion extends FormElement {
259
256
  }
260
257
  }
261
258
 
262
- public render(): TemplateResult {
259
+ protected renderWidget(): TemplateResult {
263
260
  const anchorStyles = this.anchorPosition
264
261
  ? {
265
262
  top: `${this.anchorPosition.top}px`,
@@ -270,55 +267,51 @@ export class Completion extends FormElement {
270
267
  const visible = this.options && this.options.length > 0;
271
268
 
272
269
  return html`
273
- <temba-field
274
- name=${this.name}
275
- .label=${this.label}
276
- .helpText=${this.helpText}
277
- .errors=${this.errors}
278
- .widgetOnly=${this.widgetOnly}
279
- >
280
- <div class="comp-container">
281
- <div id="anchor" style=${styleMap(anchorStyles)}></div>
282
- <temba-textinput
283
- name=${this.name}
284
- placeholder=${this.placeholder}
285
- gsm=${this.gsm}
286
- counter=${ifDefined(this.counter)}
287
- @keyup=${this.handleKeyUp}
288
- @click=${this.handleClick}
289
- @input=${this.handleInput}
290
- @blur=${this.handleOptionCanceled}
291
- maxlength="${ifDefined(this.maxLength)}"
292
- .value=${this.value}
293
- ?autogrow=${this.autogrow}
294
- ?textarea=${this.textarea}
295
- ?submitOnEnter=${this.submitOnEnter}
296
- style=${this.minHeight
297
- ? `--textarea-min-height: ${this.minHeight}px`
298
- : ''}
299
- >
300
- </temba-textinput>
301
- <temba-options
302
- @temba-selection=${this.handleOptionSelection}
303
- @temba-canceled=${this.handleOptionCanceled}
304
- .renderOption=${renderCompletionOption}
305
- .anchorTo=${this.anchorElement}
306
- .options=${this.options}
307
- ?visible=${visible}
308
- >
309
- ${this.currentFunction
310
- ? html`
311
- <div class="current-fn">
312
- ${renderCompletionOption(this.currentFunction, true)}
313
- </div>
314
- `
315
- : null}
316
- <div class="footer" style="${!visible ? 'display:none' : null}">
317
- ${msg('Tab to complete, enter to select')}
318
- </div>
319
- </temba-options>
320
- </div>
321
- </temba-field>
270
+ <div class="comp-container">
271
+ <div id="anchor" style=${styleMap(anchorStyles)}></div>
272
+ <temba-textinput
273
+ name=${this.name}
274
+ placeholder=${this.placeholder}
275
+ gsm=${this.gsm}
276
+ counter=${ifDefined(this.counter)}
277
+ @keyup=${this.handleKeyUp}
278
+ @click=${this.handleClick}
279
+ @input=${this.handleInput}
280
+ @blur=${this.handleOptionCanceled}
281
+ maxlength="${ifDefined(this.maxLength)}"
282
+ .value=${this.value}
283
+ ?autogrow=${this.autogrow}
284
+ ?textarea=${this.textarea}
285
+ ?submitOnEnter=${this.submitOnEnter}
286
+ style=${this.minHeight
287
+ ? `--textarea-min-height: ${this.minHeight}px`
288
+ : ''}
289
+ >
290
+ </temba-textinput>
291
+ <temba-options
292
+ @temba-selection=${this.handleOptionSelection}
293
+ @temba-canceled=${this.handleOptionCanceled}
294
+ .renderOption=${renderCompletionOption}
295
+ .anchorTo=${this.anchorElement}
296
+ .options=${this.options}
297
+ ?visible=${visible}
298
+ >
299
+ ${this.currentFunction
300
+ ? html`
301
+ <div class="current-fn">
302
+ ${renderCompletionOption(this.currentFunction, true)}
303
+ </div>
304
+ `
305
+ : null}
306
+ <div class="footer" style="${!visible ? 'display:none' : null}">
307
+ ${msg('Tab to complete, enter to select')}
308
+ </div>
309
+ </temba-options>
310
+ </div>
322
311
  `;
323
312
  }
313
+
314
+ public render(): TemplateResult {
315
+ return this.renderField();
316
+ }
324
317
  }
@@ -1,5 +1,5 @@
1
1
  import { TemplateResult, html, css } from 'lit';
2
- import { FormElement } from './FormElement';
2
+ import { FieldElement } from './FieldElement';
3
3
  import { property } from 'lit/decorators.js';
4
4
  import { Attachment, CustomEventType, Language, Shortcut } from '../interfaces';
5
5
  import { DEFAULT_MEDIA_ENDPOINT, getClasses } from '../utils';
@@ -20,7 +20,7 @@ export interface ComposeValue {
20
20
  variables: string[];
21
21
  }
22
22
 
23
- export class Compose extends FormElement {
23
+ export class Compose extends FieldElement {
24
24
  static get styles() {
25
25
  return css`
26
26
  :host {
@@ -544,12 +544,12 @@ export class Compose extends FormElement {
544
544
  }
545
545
 
546
546
  public render(): TemplateResult {
547
+ return this.renderField();
548
+ }
549
+
550
+ protected renderWidget(): TemplateResult {
547
551
  return html`
548
- <temba-field
549
- name=${this.name}
550
- .errors=${this.errors}
551
- .widgetOnly=${this.widgetOnly}
552
- .value=${this.value}
552
+ <div
553
553
  class=${getClasses({
554
554
  'active-template':
555
555
  !!this.currentTemplate &&
@@ -570,7 +570,7 @@ export class Compose extends FormElement {
570
570
  <div class="container">
571
571
  <div class="items actions">${this.getActions()}</div>
572
572
  </div>
573
- </temba-field>
573
+ </div>
574
574
  `;
575
575
  }
576
576
 
@@ -5,7 +5,7 @@ import { getClasses, postJSON, stopEvent, WebResponse } from '../utils';
5
5
  import { TextInput } from './TextInput';
6
6
  import '../display/Alert';
7
7
  import { Contact, CustomEventType } from '../interfaces';
8
- import { FormElement } from './FormElement';
8
+ import { FieldElement } from './FieldElement';
9
9
  import { Checkbox } from './Checkbox';
10
10
  import { msg } from '@lit/localize';
11
11
  import { OmniOption } from './select/Omnibox';
@@ -23,7 +23,7 @@ interface SummaryResponse {
23
23
  blockers: string[];
24
24
  }
25
25
 
26
- export class ContactSearch extends FormElement {
26
+ export class ContactSearch extends FieldElement {
27
27
  static get styles() {
28
28
  return css`
29
29
  :host {
@@ -475,7 +475,7 @@ export class ContactSearch extends FormElement {
475
475
  }
476
476
  }
477
477
 
478
- public render(): TemplateResult {
478
+ public renderWidget(): TemplateResult {
479
479
  let summary: TemplateResult;
480
480
  if (this.summary) {
481
481
  if (!this.summary.error) {
@@ -539,7 +539,6 @@ export class ContactSearch extends FormElement {
539
539
  this.advanced
540
540
  ? html`<div class="query">
541
541
  <temba-textinput
542
- .label=${this.label}
543
542
  .helpText=${this.helpText}
544
543
  .widgetOnly=${this.widgetOnly}
545
544
  .errors=${this.errors}