@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
@@ -1,12 +1,13 @@
1
1
  import { TemplateResult, html, css, PropertyValueMap } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
- import { FormElement } from './FormElement';
3
+ import { FieldElement } from './FieldElement';
4
4
  import { getClasses } from '../utils';
5
5
  import { DateTime } from 'luxon';
6
6
 
7
- export class DatePicker extends FormElement {
7
+ export class DatePicker extends FieldElement {
8
8
  static get styles() {
9
9
  return css`
10
+ ${super.styles}
10
11
  :host {
11
12
  display: block;
12
13
  }
@@ -205,7 +206,7 @@ export class DatePicker extends FormElement {
205
206
  this.shadowRoot.querySelector('input').focus();
206
207
  }
207
208
 
208
- public render(): TemplateResult {
209
+ protected renderWidget(): TemplateResult {
209
210
  const classes = getClasses({ unset: !this.value });
210
211
 
211
212
  let dateWidgetValue = null;
@@ -216,42 +217,35 @@ export class DatePicker extends FormElement {
216
217
  }
217
218
 
218
219
  return html`
219
- <temba-field
220
- class=${getClasses({ disabled: this.disabled })}
221
- name=${this.name}
222
- .label="${this.label}"
223
- .helpText="${this.helpText}"
224
- .errors=${this.errors}
225
- .widgetOnly=${this.widgetOnly}
226
- .hideLabel=${this.hideLabel}
227
- .disabled=${this.disabled}
228
- >
229
- <div class="container" @click=${this.handleClicked}>
230
- <slot name="prefix"></slot>
231
- <div class="input-wrapper">
232
- <input
233
- class=${classes}
234
- name=${this.label}
235
- value=${dateWidgetValue}
236
- type="${this.time ? 'datetime-local' : 'date'}"
237
- @change=${this.handleChange}
238
- min=${this.min || undefined}
239
- max=${this.max || undefined}
240
- />
241
- </div>
242
- ${this.time
243
- ? html`
244
- <div class="tz-wrapper">
245
- <div class="tz">
246
- <div class="label">Time Zone</div>
247
- <div class="zone">${this.timezoneFriendly}</div>
248
- </div>
249
- </div>
250
- `
251
- : null}
252
- <slot name="postfix"></slot>
220
+ <div class="container" @click=${this.handleClicked}>
221
+ <slot name="prefix"></slot>
222
+ <div class="input-wrapper">
223
+ <input
224
+ class=${classes}
225
+ name=${this.label}
226
+ value=${dateWidgetValue}
227
+ type="${this.time ? 'datetime-local' : 'date'}"
228
+ @change=${this.handleChange}
229
+ min=${this.min || undefined}
230
+ max=${this.max || undefined}
231
+ />
253
232
  </div>
254
- </temba-field>
233
+ ${this.time
234
+ ? html`
235
+ <div class="tz-wrapper">
236
+ <div class="tz">
237
+ <div class="label">Time Zone</div>
238
+ <div class="zone">${this.timezoneFriendly}</div>
239
+ </div>
240
+ </div>
241
+ `
242
+ : null}
243
+ <slot name="postfix"></slot>
244
+ </div>
255
245
  `;
256
246
  }
247
+
248
+ public render(): TemplateResult {
249
+ return this.renderField();
250
+ }
257
251
  }
@@ -1,12 +1,65 @@
1
- import { TemplateResult, html, css, LitElement } from 'lit';
1
+ import { TemplateResult, html, css } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
+ import { RapidElement } from '../RapidElement';
3
4
  import { renderMarkdownInline } from '../markdown';
4
5
 
5
6
  /**
6
- * A small wrapper to display labels and help text in a smartmin style.
7
- * This exists so we can display things consistently before restyling.
7
+ * FieldElement is a base class for form components that provides built-in
8
+ * field wrapper functionality, eliminating the need for manual temba-field embedding.
9
+ *
10
+ * Components extending this class only need to implement renderWidget() with their
11
+ * specific widget content, and the field wrapper (label, errors, help text) is
12
+ * automatically handled.
8
13
  */
9
- export class FormField extends LitElement {
14
+ export abstract class FieldElement extends RapidElement {
15
+ @property({ type: String })
16
+ name = '';
17
+
18
+ @property({ type: String, attribute: 'help_text' })
19
+ helpText: string;
20
+
21
+ @property({ type: Boolean, attribute: 'widget_only' })
22
+ widgetOnly: boolean;
23
+
24
+ @property({ type: Array })
25
+ errors: string[];
26
+
27
+ // Use @property with custom getter/setter to handle both attribute and programmatic access
28
+ private _value: any = '';
29
+
30
+ @property({ type: String })
31
+ public get value() {
32
+ return this._value;
33
+ }
34
+
35
+ public set value(value) {
36
+ this._value = value;
37
+ }
38
+
39
+ @property({ attribute: false })
40
+ inputRoot: HTMLElement = this;
41
+
42
+ @property({ type: Boolean })
43
+ disabled = false;
44
+
45
+ static formAssociated = true;
46
+
47
+ protected internals: ElementInternals;
48
+
49
+ @property({ type: Boolean })
50
+ hideErrors = false;
51
+
52
+ @property({ type: Boolean, attribute: 'hide_label' })
53
+ hideLabel: boolean;
54
+
55
+ @property({ type: String })
56
+ label: string;
57
+
58
+ constructor() {
59
+ super();
60
+ this.internals = this.attachInternals();
61
+ }
62
+
10
63
  static get styles() {
11
64
  return css`
12
65
  :host {
@@ -29,19 +82,6 @@ export class FormField extends LitElement {
29
82
  line-height: normal;
30
83
  color: var(--color-text-help);
31
84
  margin-left: var(--help-text-margin-left);
32
- margin-top: -16px;
33
- opacity: 0;
34
- transition: opacity ease-in-out 100ms, margin-top ease-in-out 200ms;
35
- pointer-events: none;
36
- }
37
-
38
- .help-text.help-always {
39
- opacity: 1;
40
- margin-top: 6px;
41
- margin-left: var(--help-text-margin-left);
42
- }
43
-
44
- .field:focus-within .help-text {
45
85
  margin-top: 6px;
46
86
  opacity: 1;
47
87
  }
@@ -151,36 +191,13 @@ export class FormField extends LitElement {
151
191
  `;
152
192
  }
153
193
 
154
- @property({ type: Boolean, attribute: 'hide_label' })
155
- hideLabel = false;
156
-
157
- @property({ type: Boolean, attribute: 'widget_only' })
158
- widgetOnly = false;
159
-
160
- @property({ type: Array, attribute: false })
161
- errors: string[] = [];
162
-
163
- @property({ type: Boolean })
164
- hideErrors = false;
165
-
166
- @property({ type: String, attribute: 'help_text' })
167
- helpText = '';
168
-
169
- @property({ type: Boolean, attribute: 'help_always' })
170
- helpAlways = true;
171
-
172
- @property({ type: String })
173
- label = '';
174
-
175
- @property({ type: String })
176
- name = '';
177
-
178
- @property({ type: Boolean })
179
- disabled = false;
180
-
181
- updated(changedProperties: Map<string | number | symbol, unknown>): void {
194
+ updated(changedProperties: Map<string, any>): void {
182
195
  super.updated(changedProperties);
183
196
 
197
+ if (changedProperties.has('value')) {
198
+ this.internals.setFormValue(this.value);
199
+ }
200
+
184
201
  if (
185
202
  changedProperties.has('errors') ||
186
203
  changedProperties.has('hideErrors')
@@ -191,7 +208,36 @@ export class FormField extends LitElement {
191
208
  }
192
209
  }
193
210
 
194
- public render(): TemplateResult {
211
+ get form() {
212
+ return this.internals.form;
213
+ }
214
+
215
+ public setValue(value: any) {
216
+ this.value = this.serializeValue(value);
217
+ }
218
+
219
+ public getDeserializedValue(): any {
220
+ if (!this.value || this.value === '') {
221
+ return null;
222
+ }
223
+ return JSON.parse(this.value);
224
+ }
225
+
226
+ public serializeValue(value: any): string {
227
+ return JSON.stringify(value);
228
+ }
229
+
230
+ /**
231
+ * Abstract method that components must implement to render their specific widget content.
232
+ * This replaces the need to manually embed temba-field.
233
+ */
234
+ protected abstract renderWidget(): TemplateResult;
235
+
236
+ /**
237
+ * Renders the complete field including label, widget, errors, and help text.
238
+ * Components can override this for custom layouts, but typically should just implement renderWidget().
239
+ */
240
+ protected renderField(): TemplateResult {
195
241
  const hasErrors = !this.hideErrors && this.errors && this.errors.length > 0;
196
242
  const errors = hasErrors
197
243
  ? this.errors.map((error: string) => {
@@ -203,7 +249,9 @@ export class FormField extends LitElement {
203
249
 
204
250
  if (this.widgetOnly) {
205
251
  return html`
206
- <div class="${this.disabled ? 'disabled' : ''}"><slot></slot></div>
252
+ <div class="${this.disabled ? 'disabled' : ''}">
253
+ ${this.renderWidget()}
254
+ </div>
207
255
  ${errors}
208
256
  `;
209
257
  }
@@ -221,13 +269,10 @@ export class FormField extends LitElement {
221
269
  >
222
270
  `
223
271
  : null}
224
- <div class="widget">
225
- <slot></slot>
226
- ${errors}
227
- </div>
272
+ <div class="widget">${this.renderWidget()} ${errors}</div>
228
273
  ${this.helpText && this.helpText !== 'None'
229
274
  ? html`
230
- <div class="help-text ${this.helpAlways ? 'help-always' : null}">
275
+ <div class="help-text">
231
276
  ${renderMarkdownInline(this.helpText)}
232
277
  </div>
233
278
  `
@@ -235,4 +280,12 @@ export class FormField extends LitElement {
235
280
  </div>
236
281
  `;
237
282
  }
283
+
284
+ /**
285
+ * Main render method that automatically provides field wrapper functionality.
286
+ * Components extending FieldElement should not override this unless they need custom field layouts.
287
+ */
288
+ public render(): TemplateResult {
289
+ return this.renderField();
290
+ }
238
291
  }
@@ -385,8 +385,9 @@ export class FieldRenderer {
385
385
  } = context;
386
386
 
387
387
  return html`<div class="form-field">
388
- ${showLabel ? html`<label>${config.label}</label>` : ''}
389
388
  <temba-array-editor
389
+ name="${fieldName}"
390
+ .label="${showLabel ? config.label : ''}"
390
391
  .value="${value || []}"
391
392
  .itemConfig="${config.itemConfig}"
392
393
  .sortable="${config.sortable}"
@@ -3,120 +3,123 @@ import { html, css, PropertyValueMap } from 'lit';
3
3
  import { CroppieCSS } from './CroppieCSS';
4
4
  import { property } from 'lit/decorators.js';
5
5
  import { Icon } from '../Icons';
6
- import { FormElement } from './FormElement';
7
-
8
- export class ImagePicker extends FormElement {
9
- static styles = css`
10
- ${CroppieCSS}
11
-
12
- .croppie {
13
- max-width: 400px;
14
- border: 0px solid #ccc;
15
- border-radius: 0.5em;
16
- overflow: hidden;
17
- background: #fff;
18
- margin-top: -20%;
19
- box-shadow: 0 0 15px 5px rgba(0, 0, 0, 0.1);
20
- }
6
+ import { FieldElement } from './FieldElement';
7
+
8
+ export class ImagePicker extends FieldElement {
9
+ static get styles() {
10
+ return css`
11
+ ${super.styles}
12
+ ${CroppieCSS}
13
+
14
+ .croppie {
15
+ max-width: 400px;
16
+ border: 0px solid #ccc;
17
+ border-radius: 0.5em;
18
+ overflow: hidden;
19
+ background: #fff;
20
+ margin-top: -20%;
21
+ box-shadow: 0 0 15px 5px rgba(0, 0, 0, 0.1);
22
+ }
21
23
 
22
- .croppie .controls {
23
- display: flex;
24
- align-items: center;
25
- flex-direction: row;
26
- justify-content: center;
27
- position: absolute;
28
- z-index: 1;
29
- width: 400px;
30
- margin-top: -42px;
31
- }
24
+ .croppie .controls {
25
+ display: flex;
26
+ align-items: center;
27
+ flex-direction: row;
28
+ justify-content: center;
29
+ position: absolute;
30
+ z-index: 1;
31
+ width: 400px;
32
+ margin-top: -42px;
33
+ }
32
34
 
33
- .toggle {
34
- height: 110px;
35
- width: 110px;
36
- cursor: pointer;
37
- display: flex;
38
- align-items: center;
39
- justify-content: center;
40
- }
35
+ .toggle {
36
+ height: 110px;
37
+ width: 110px;
38
+ cursor: pointer;
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ }
41
43
 
42
- .circle .toggle {
43
- border-radius: 50%;
44
- }
44
+ .circle .toggle {
45
+ border-radius: 50%;
46
+ }
45
47
 
46
- .toggle.set {
47
- box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 7px 0px,
48
- rgba(0, 0, 0, 0.2) 0px 1px 2px 0px, inset 0 0 0 5px rgba(0, 0, 0, 0.1);
49
- }
48
+ .toggle.set {
49
+ box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 7px 0px,
50
+ rgba(0, 0, 0, 0.2) 0px 1px 2px 0px, inset 0 0 0 5px rgba(0, 0, 0, 0.1);
51
+ }
50
52
 
51
- .toggle.set:hover {
52
- box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 7px 0px,
53
- rgba(0, 0, 0, 0.2) 0px 1px 2px 0px, inset 0 0 0 5px rgba(0, 0, 0, 0.2);
54
- }
53
+ .toggle.set:hover {
54
+ box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 7px 0px,
55
+ rgba(0, 0, 0, 0.2) 0px 1px 2px 0px, inset 0 0 0 5px rgba(0, 0, 0, 0.2);
56
+ }
55
57
 
56
- .toggle temba-icon {
57
- color: rgba(0, 0, 0, 0.2);
58
- padding: 5px;
59
- }
58
+ .toggle temba-icon {
59
+ color: rgba(0, 0, 0, 0.2);
60
+ padding: 5px;
61
+ }
60
62
 
61
- toggle:hover temba-icon {
62
- color: rgba(0, 0, 0, 0.8);
63
- }
63
+ toggle:hover temba-icon {
64
+ color: rgba(0, 0, 0, 0.8);
65
+ }
64
66
 
65
- .toggle.set temba-icon {
66
- border-radius: 50%;
67
- margin-right: -90%;
68
- margin-bottom: -50%;
69
- background: rgba(240, 240, 240, 1);
70
- box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 2px 0px;
71
- }
67
+ .toggle.set temba-icon {
68
+ border-radius: 50%;
69
+ margin-right: -90%;
70
+ margin-bottom: -50%;
71
+ background: rgba(240, 240, 240, 1);
72
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 2px 0px;
73
+ }
72
74
 
73
- .toggle.set:hover temba-icon {
74
- background: #fff;
75
- color: var(--color-primary-dark);
76
- }
75
+ .toggle.set:hover temba-icon {
76
+ background: #fff;
77
+ color: var(--color-primary-dark);
78
+ }
77
79
 
78
- .circle .toggle.set temba-icon {
79
- margin-right: -70%;
80
- margin-bottom: -70%;
81
- }
80
+ .circle .toggle.set temba-icon {
81
+ margin-right: -70%;
82
+ margin-bottom: -70%;
83
+ }
82
84
 
83
- .hidden {
84
- display: none;
85
- }
85
+ .hidden {
86
+ display: none;
87
+ }
86
88
 
87
- .controls temba-icon {
88
- margin: 0em 0.75em;
89
- background: rgba(255, 255, 255, 0.8);
90
- border-radius: 50%;
91
- padding: 6px;
92
- transition: all 0.1s ease-in-out;
93
- }
89
+ .controls temba-icon {
90
+ margin: 0em 0.75em;
91
+ background: rgba(255, 255, 255, 0.8);
92
+ border-radius: 50%;
93
+ padding: 6px;
94
+ transition: all 0.1s ease-in-out;
95
+ }
94
96
 
95
- .controls {
96
- pointer-events: none;
97
- display: flex;
98
- }
97
+ .controls {
98
+ pointer-events: none;
99
+ display: flex;
100
+ }
99
101
 
100
- .controls temba-icon {
101
- pointer-events: all;
102
- }
102
+ .controls temba-icon {
103
+ pointer-events: all;
104
+ }
103
105
 
104
- .controls temba-icon.close {
105
- color: rgba(0, 0, 0, 0.2);
106
- background: rgba(255, 255, 255, 0.2);
107
- }
106
+ .controls temba-icon.close {
107
+ color: rgba(0, 0, 0, 0.2);
108
+ background: rgba(255, 255, 255, 0.2);
109
+ }
108
110
 
109
- .controls temba-icon.submit {
110
- color: rgba(0, 0, 0, 0.2);
111
- box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.1);
112
- }
111
+ .controls temba-icon.submit {
112
+ color: rgba(0, 0, 0, 0.2);
113
+ box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.1);
114
+ }
113
115
 
114
- .controls temba-icon:hover {
115
- color: white;
116
- cursor: pointer;
117
- background: var(--color-primary-dark);
118
- }
119
- `;
116
+ .controls temba-icon:hover {
117
+ color: white;
118
+ cursor: pointer;
119
+ background: var(--color-primary-dark);
120
+ }
121
+ `;
122
+ }
120
123
 
121
124
  @property({ type: String })
122
125
  tempImage: string;
@@ -221,18 +224,9 @@ export class ImagePicker extends FormElement {
221
224
  input.value = '';
222
225
  }
223
226
 
224
- protected render() {
227
+ protected renderWidget() {
225
228
  return html`
226
- <div class="wrapper ${this.shape} ${this.label ? 'label' : ''}">
227
- <temba-field
228
- name=${this.name}
229
- label=${this.label}
230
- .helpText=${this.helpText}
231
- .errors=${this.errors}
232
- .widgetOnly=${this.widgetOnly}
233
- .helpAlways=${true}
234
- ?disabled=${this.disabled}
235
- >
229
+ <div class="wrapper ${this.shape} ${this.label ? 'label' : ''}">
236
230
  <input class='hidden' type="file" accept="image/*" capture="camera" id="file" name="file" @change=${
237
231
  this.handleFileChanged
238
232
  }/>
@@ -261,8 +255,11 @@ export class ImagePicker extends FormElement {
261
255
  }></temba-icon>
262
256
  </div>
263
257
  </temba-mask>
264
- </temba-field>
265
- </div>
258
+ </div>
266
259
  `;
267
260
  }
261
+
262
+ public render() {
263
+ return this.renderField();
264
+ }
268
265
  }
@@ -31,13 +31,13 @@ export class KeyValueEditor extends BaseListEditor<KeyValueItem> {
31
31
 
32
32
  // External API uses array format to preserve duplicate keys
33
33
  @property({ type: Array })
34
- get value(): KeyValueItem[] {
34
+ get value(): KeyValueItem[] | any[] {
35
35
  return this._items.filter(
36
36
  ({ key, value }) => key.trim() !== '' || value.trim() !== ''
37
37
  );
38
38
  }
39
39
 
40
- set value(newValue: KeyValueItem[] | Record<string, string>) {
40
+ set value(newValue: KeyValueItem[] | Record<string, string> | any) {
41
41
  if (Array.isArray(newValue)) {
42
42
  this._items = [...newValue];
43
43
  } else {
@@ -206,46 +206,50 @@ export class KeyValueEditor extends BaseListEditor<KeyValueItem> {
206
206
  return 'key-value-editor';
207
207
  }
208
208
 
209
- static styles = css`
210
- .key-value-editor {
211
- display: flex;
212
- flex-direction: column;
213
- gap: 8px;
214
- }
209
+ static get styles() {
210
+ return css`
211
+ ${super.styles}
215
212
 
216
- .row {
217
- display: grid;
218
- grid-template-columns: 1fr 1fr auto;
219
- align-items: center;
220
- column-gap: 6px;
221
- }
213
+ .key-value-editor {
214
+ display: flex;
215
+ flex-direction: column;
216
+ gap: 8px;
217
+ }
222
218
 
223
- .remove-btn {
224
- width: 32px;
225
- height: 32px;
226
- border: 1px solid #ccc;
227
- border-radius: 4px;
228
- background: #f8f8f8;
229
- color: #666;
230
- cursor: pointer;
231
- display: flex;
232
- align-items: center;
233
- justify-content: center;
234
- font-size: 18px;
235
- }
219
+ .row {
220
+ display: grid;
221
+ grid-template-columns: 1fr 1fr auto;
222
+ align-items: center;
223
+ column-gap: 6px;
224
+ }
236
225
 
237
- .remove-btn:hover:not(:disabled) {
238
- background: #f0f0f0;
239
- }
226
+ .remove-btn {
227
+ width: 32px;
228
+ height: 32px;
229
+ border: 1px solid #ccc;
230
+ border-radius: 4px;
231
+ background: #f8f8f8;
232
+ color: #666;
233
+ cursor: pointer;
234
+ display: flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ font-size: 18px;
238
+ }
240
239
 
241
- .remove-btn:disabled {
242
- opacity: 0.5;
243
- cursor: not-allowed;
244
- }
240
+ .remove-btn:hover:not(:disabled) {
241
+ background: #f0f0f0;
242
+ }
245
243
 
246
- .remove-btn-spacer {
247
- width: 32px;
248
- height: 32px;
249
- }
250
- `;
244
+ .remove-btn:disabled {
245
+ opacity: 0.5;
246
+ cursor: not-allowed;
247
+ }
248
+
249
+ .remove-btn-spacer {
250
+ width: 32px;
251
+ height: 32px;
252
+ }
253
+ `;
254
+ }
251
255
  }