@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
@@ -4,14 +4,128 @@ import { html, css } from 'lit';
4
4
  import { CroppieCSS } from './CroppieCSS';
5
5
  import { property } from 'lit/decorators.js';
6
6
  import { Icon } from '../Icons';
7
- import { FormElement } from './FormElement';
8
- export class ImagePicker extends FormElement {
7
+ import { FieldElement } from './FieldElement';
8
+ export class ImagePicker extends FieldElement {
9
9
  constructor() {
10
10
  super(...arguments);
11
11
  this.shape = 'square';
12
12
  this.showCroppie = false;
13
13
  this.uploadReader = new FileReader();
14
14
  }
15
+ static get styles() {
16
+ return css `
17
+ ${super.styles}
18
+ ${CroppieCSS}
19
+
20
+ .croppie {
21
+ max-width: 400px;
22
+ border: 0px solid #ccc;
23
+ border-radius: 0.5em;
24
+ overflow: hidden;
25
+ background: #fff;
26
+ margin-top: -20%;
27
+ box-shadow: 0 0 15px 5px rgba(0, 0, 0, 0.1);
28
+ }
29
+
30
+ .croppie .controls {
31
+ display: flex;
32
+ align-items: center;
33
+ flex-direction: row;
34
+ justify-content: center;
35
+ position: absolute;
36
+ z-index: 1;
37
+ width: 400px;
38
+ margin-top: -42px;
39
+ }
40
+
41
+ .toggle {
42
+ height: 110px;
43
+ width: 110px;
44
+ cursor: pointer;
45
+ display: flex;
46
+ align-items: center;
47
+ justify-content: center;
48
+ }
49
+
50
+ .circle .toggle {
51
+ border-radius: 50%;
52
+ }
53
+
54
+ .toggle.set {
55
+ box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 7px 0px,
56
+ rgba(0, 0, 0, 0.2) 0px 1px 2px 0px, inset 0 0 0 5px rgba(0, 0, 0, 0.1);
57
+ }
58
+
59
+ .toggle.set:hover {
60
+ box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 7px 0px,
61
+ rgba(0, 0, 0, 0.2) 0px 1px 2px 0px, inset 0 0 0 5px rgba(0, 0, 0, 0.2);
62
+ }
63
+
64
+ .toggle temba-icon {
65
+ color: rgba(0, 0, 0, 0.2);
66
+ padding: 5px;
67
+ }
68
+
69
+ toggle:hover temba-icon {
70
+ color: rgba(0, 0, 0, 0.8);
71
+ }
72
+
73
+ .toggle.set temba-icon {
74
+ border-radius: 50%;
75
+ margin-right: -90%;
76
+ margin-bottom: -50%;
77
+ background: rgba(240, 240, 240, 1);
78
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 2px 0px;
79
+ }
80
+
81
+ .toggle.set:hover temba-icon {
82
+ background: #fff;
83
+ color: var(--color-primary-dark);
84
+ }
85
+
86
+ .circle .toggle.set temba-icon {
87
+ margin-right: -70%;
88
+ margin-bottom: -70%;
89
+ }
90
+
91
+ .hidden {
92
+ display: none;
93
+ }
94
+
95
+ .controls temba-icon {
96
+ margin: 0em 0.75em;
97
+ background: rgba(255, 255, 255, 0.8);
98
+ border-radius: 50%;
99
+ padding: 6px;
100
+ transition: all 0.1s ease-in-out;
101
+ }
102
+
103
+ .controls {
104
+ pointer-events: none;
105
+ display: flex;
106
+ }
107
+
108
+ .controls temba-icon {
109
+ pointer-events: all;
110
+ }
111
+
112
+ .controls temba-icon.close {
113
+ color: rgba(0, 0, 0, 0.2);
114
+ background: rgba(255, 255, 255, 0.2);
115
+ }
116
+
117
+ .controls temba-icon.submit {
118
+ color: rgba(0, 0, 0, 0.2);
119
+ box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.1);
120
+ }
121
+
122
+ .controls temba-icon:hover {
123
+ color: white;
124
+ cursor: pointer;
125
+ background: var(--color-primary-dark);
126
+ }
127
+ `;
128
+ }
15
129
  firstUpdated(changed) {
16
130
  super.firstUpdated(changed);
17
131
  const picker = this;
@@ -86,18 +200,9 @@ export class ImagePicker extends FormElement {
86
200
  }
87
201
  input.value = '';
88
202
  }
89
- render() {
203
+ renderWidget() {
90
204
  return html `
91
- <div class="wrapper ${this.shape} ${this.label ? 'label' : ''}">
92
- <temba-field
93
- name=${this.name}
94
- label=${this.label}
95
- .helpText=${this.helpText}
96
- .errors=${this.errors}
97
- .widgetOnly=${this.widgetOnly}
98
- .helpAlways=${true}
99
- ?disabled=${this.disabled}
100
- >
205
+ <div class="wrapper ${this.shape} ${this.label ? 'label' : ''}">
101
206
  <input class='hidden' type="file" accept="image/*" capture="camera" id="file" name="file" @change=${this.handleFileChanged}/>
102
207
  <div class='toggle ${this.url ? 'set' : ''} ${this.showCroppie ? 'hidden' : ''}' @click=${this.handleToggleClicked} style="background: ${this.url
103
208
  ? `url('${this.url}') center / contain no-repeat`
@@ -114,122 +219,13 @@ export class ImagePicker extends FormElement {
114
219
  <temba-icon class="submit" size="1" name=${Icon.submit} @click=${this.saveResult}></temba-icon>
115
220
  </div>
116
221
  </temba-mask>
117
- </temba-field>
118
- </div>
222
+ </div>
119
223
  `;
120
224
  }
121
- }
122
- ImagePicker.styles = css `
123
- ${CroppieCSS}
124
-
125
- .croppie {
126
- max-width: 400px;
127
- border: 0px solid #ccc;
128
- border-radius: 0.5em;
129
- overflow: hidden;
130
- background: #fff;
131
- margin-top: -20%;
132
- box-shadow: 0 0 15px 5px rgba(0, 0, 0, 0.1);
133
- }
134
-
135
- .croppie .controls {
136
- display: flex;
137
- align-items: center;
138
- flex-direction: row;
139
- justify-content: center;
140
- position: absolute;
141
- z-index: 1;
142
- width: 400px;
143
- margin-top: -42px;
144
- }
145
-
146
- .toggle {
147
- height: 110px;
148
- width: 110px;
149
- cursor: pointer;
150
- display: flex;
151
- align-items: center;
152
- justify-content: center;
153
- }
154
-
155
- .circle .toggle {
156
- border-radius: 50%;
157
- }
158
-
159
- .toggle.set {
160
- box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 7px 0px,
161
- rgba(0, 0, 0, 0.2) 0px 1px 2px 0px, inset 0 0 0 5px rgba(0, 0, 0, 0.1);
162
- }
163
-
164
- .toggle.set:hover {
165
- box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 7px 0px,
166
- rgba(0, 0, 0, 0.2) 0px 1px 2px 0px, inset 0 0 0 5px rgba(0, 0, 0, 0.2);
167
- }
168
-
169
- .toggle temba-icon {
170
- color: rgba(0, 0, 0, 0.2);
171
- padding: 5px;
172
- }
173
-
174
- toggle:hover temba-icon {
175
- color: rgba(0, 0, 0, 0.8);
176
- }
177
-
178
- .toggle.set temba-icon {
179
- border-radius: 50%;
180
- margin-right: -90%;
181
- margin-bottom: -50%;
182
- background: rgba(240, 240, 240, 1);
183
- box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 2px 0px;
184
- }
185
-
186
- .toggle.set:hover temba-icon {
187
- background: #fff;
188
- color: var(--color-primary-dark);
189
- }
190
-
191
- .circle .toggle.set temba-icon {
192
- margin-right: -70%;
193
- margin-bottom: -70%;
194
- }
195
-
196
- .hidden {
197
- display: none;
198
- }
199
-
200
- .controls temba-icon {
201
- margin: 0em 0.75em;
202
- background: rgba(255, 255, 255, 0.8);
203
- border-radius: 50%;
204
- padding: 6px;
205
- transition: all 0.1s ease-in-out;
206
- }
207
-
208
- .controls {
209
- pointer-events: none;
210
- display: flex;
211
- }
212
-
213
- .controls temba-icon {
214
- pointer-events: all;
215
- }
216
-
217
- .controls temba-icon.close {
218
- color: rgba(0, 0, 0, 0.2);
219
- background: rgba(255, 255, 255, 0.2);
220
- }
221
-
222
- .controls temba-icon.submit {
223
- color: rgba(0, 0, 0, 0.2);
224
- box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.1);
225
- }
226
-
227
- .controls temba-icon:hover {
228
- color: white;
229
- cursor: pointer;
230
- background: var(--color-primary-dark);
225
+ render() {
226
+ return this.renderField();
231
227
  }
232
- `;
228
+ }
233
229
  __decorate([
234
230
  property({ type: String })
235
231
  ], ImagePicker.prototype, "tempImage", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"ImagePicker.js","sourceRoot":"","sources":["../../../src/form/ImagePicker.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-this-alias */\nimport { html, css, PropertyValueMap } from 'lit';\nimport { CroppieCSS } from './CroppieCSS';\nimport { property } from 'lit/decorators.js';\nimport { Icon } from '../Icons';\nimport { FormElement } from './FormElement';\n\nexport class ImagePicker extends FormElement {\n static styles = css`\n ${CroppieCSS}\n\n .croppie {\n max-width: 400px;\n border: 0px solid #ccc;\n border-radius: 0.5em;\n overflow: hidden;\n background: #fff;\n margin-top: -20%;\n box-shadow: 0 0 15px 5px rgba(0, 0, 0, 0.1);\n }\n\n .croppie .controls {\n display: flex;\n align-items: center;\n flex-direction: row;\n justify-content: center;\n position: absolute;\n z-index: 1;\n width: 400px;\n margin-top: -42px;\n }\n\n .toggle {\n height: 110px;\n width: 110px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .circle .toggle {\n border-radius: 50%;\n }\n\n .toggle.set {\n box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 7px 0px,\n rgba(0, 0, 0, 0.2) 0px 1px 2px 0px, inset 0 0 0 5px rgba(0, 0, 0, 0.1);\n }\n\n .toggle.set:hover {\n box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 7px 0px,\n rgba(0, 0, 0, 0.2) 0px 1px 2px 0px, inset 0 0 0 5px rgba(0, 0, 0, 0.2);\n }\n\n .toggle temba-icon {\n color: rgba(0, 0, 0, 0.2);\n padding: 5px;\n }\n\n toggle:hover temba-icon {\n color: rgba(0, 0, 0, 0.8);\n }\n\n .toggle.set temba-icon {\n border-radius: 50%;\n margin-right: -90%;\n margin-bottom: -50%;\n background: rgba(240, 240, 240, 1);\n box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 2px 0px;\n }\n\n .toggle.set:hover temba-icon {\n background: #fff;\n color: var(--color-primary-dark);\n }\n\n .circle .toggle.set temba-icon {\n margin-right: -70%;\n margin-bottom: -70%;\n }\n\n .hidden {\n display: none;\n }\n\n .controls temba-icon {\n margin: 0em 0.75em;\n background: rgba(255, 255, 255, 0.8);\n border-radius: 50%;\n padding: 6px;\n transition: all 0.1s ease-in-out;\n }\n\n .controls {\n pointer-events: none;\n display: flex;\n }\n\n .controls temba-icon {\n pointer-events: all;\n }\n\n .controls temba-icon.close {\n color: rgba(0, 0, 0, 0.2);\n background: rgba(255, 255, 255, 0.2);\n }\n\n .controls temba-icon.submit {\n color: rgba(0, 0, 0, 0.2);\n box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.1);\n }\n\n .controls temba-icon:hover {\n color: white;\n cursor: pointer;\n background: var(--color-primary-dark);\n }\n `;\n\n @property({ type: String })\n tempImage: string;\n\n @property({ type: String })\n url: string;\n\n @property({ type: String })\n shape = 'square';\n\n @property({ type: Boolean, attribute: false })\n showCroppie = false;\n\n uploadReader = new FileReader();\n croppie: any;\n\n protected firstUpdated(\n changed: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.firstUpdated(changed);\n const picker = this;\n\n this.uploadReader.onload = function () {\n picker.launchCroppie(picker.uploadReader.result as any);\n };\n }\n\n public updated(changed: Map<string, any>): void {\n super.updated(changed);\n if (changed.has('url')) {\n this.setAttribute('url', this.url);\n }\n }\n\n private closeCroppie() {\n this.showCroppie = false;\n const wrapper = this.shadowRoot.querySelector('.croppie .embed');\n if (wrapper.firstChild) {\n wrapper.removeChild(wrapper.firstChild);\n }\n }\n\n private launchCroppie(url: string) {\n const wrapper = this.shadowRoot.querySelector('.croppie .embed');\n if (wrapper.firstChild) {\n wrapper.removeChild(wrapper.firstChild);\n }\n this.showCroppie = true;\n const ele = document.createElement('div');\n wrapper.appendChild(ele);\n\n const Croppie = (window as any).Croppie;\n this.croppie = new Croppie(ele, {\n enableExif: true,\n viewport: {\n width: 300,\n height: 300,\n type: this.shape\n },\n boundary: {\n width: 400,\n height: 400\n }\n });\n\n this.croppie.bind({ url });\n }\n\n private saveResult() {\n const picker = this;\n this.croppie\n .result({\n type: 'blob',\n size: 'viewport',\n format: 'webp',\n quality: 1,\n circle: false\n })\n .then(function (resp: any) {\n const blob = resp;\n picker.url = URL.createObjectURL(blob);\n\n // const blob = dataURItoBlob(resp);\n const fd = new FormData();\n fd.append(picker.name, blob, 'filename.webp');\n\n picker.value = fd;\n picker.closeCroppie();\n });\n }\n\n private handleToggleClicked() {\n const fileInput = this.shadowRoot.querySelector('#file');\n (fileInput as any).click();\n }\n\n private handleFileChanged(e: Event) {\n const input = e.target as any;\n if (input.files.length > 0) {\n this.uploadReader.readAsDataURL(input.files[0]);\n }\n input.value = '';\n }\n\n protected render() {\n return html`\n <div class=\"wrapper ${this.shape} ${this.label ? 'label' : ''}\">\n <temba-field\n name=${this.name}\n label=${this.label}\n .helpText=${this.helpText}\n .errors=${this.errors}\n .widgetOnly=${this.widgetOnly}\n .helpAlways=${true}\n ?disabled=${this.disabled}\n >\n <input class='hidden' type=\"file\" accept=\"image/*\" capture=\"camera\" id=\"file\" name=\"file\" @change=${\n this.handleFileChanged\n }/>\n <div class='toggle ${this.url ? 'set' : ''} ${\n this.showCroppie ? 'hidden' : ''\n }' @click=${this.handleToggleClicked} style=\"background: ${\n this.url\n ? `url('${this.url}') center / contain no-repeat`\n : 'rgba(0, 0, 0, 0.1)'\n }\">\n <temba-icon name=${Icon.upload_image} size=\"1.5\"></temba-icon>\n </div>\n \n <temba-mask ?show=${this.showCroppie} class=\"${\n this.showCroppie ? 'editing' : ''\n }\">\n <div class='croppie'>\n <div class='embed'></div>\n <div class='controls'>\n <temba-icon class=\"close\" size=\"1\" name=${Icon.close} @click=${\n this.closeCroppie\n }></temba-icon>\n <div style=\"flex-grow:1\"></div>\n <temba-icon class=\"submit\" size=\"1\" name=${Icon.submit} @click=${\n this.saveResult\n }></temba-icon>\n </div>\n </temba-mask>\n </temba-field>\n </div>\n `;\n }\n}\n"]}
1
+ {"version":3,"file":"ImagePicker.js","sourceRoot":"","sources":["../../../src/form/ImagePicker.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-this-alias */\nimport { html, css, PropertyValueMap } from 'lit';\nimport { CroppieCSS } from './CroppieCSS';\nimport { property } from 'lit/decorators.js';\nimport { Icon } from '../Icons';\nimport { FieldElement } from './FieldElement';\n\nexport class ImagePicker extends FieldElement {\n static get styles() {\n return css`\n ${super.styles}\n ${CroppieCSS}\n\n .croppie {\n max-width: 400px;\n border: 0px solid #ccc;\n border-radius: 0.5em;\n overflow: hidden;\n background: #fff;\n margin-top: -20%;\n box-shadow: 0 0 15px 5px rgba(0, 0, 0, 0.1);\n }\n\n .croppie .controls {\n display: flex;\n align-items: center;\n flex-direction: row;\n justify-content: center;\n position: absolute;\n z-index: 1;\n width: 400px;\n margin-top: -42px;\n }\n\n .toggle {\n height: 110px;\n width: 110px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .circle .toggle {\n border-radius: 50%;\n }\n\n .toggle.set {\n box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 7px 0px,\n rgba(0, 0, 0, 0.2) 0px 1px 2px 0px, inset 0 0 0 5px rgba(0, 0, 0, 0.1);\n }\n\n .toggle.set:hover {\n box-shadow: rgba(0, 0, 0, 0.1) 0px 3px 7px 0px,\n rgba(0, 0, 0, 0.2) 0px 1px 2px 0px, inset 0 0 0 5px rgba(0, 0, 0, 0.2);\n }\n\n .toggle temba-icon {\n color: rgba(0, 0, 0, 0.2);\n padding: 5px;\n }\n\n toggle:hover temba-icon {\n color: rgba(0, 0, 0, 0.8);\n }\n\n .toggle.set temba-icon {\n border-radius: 50%;\n margin-right: -90%;\n margin-bottom: -50%;\n background: rgba(240, 240, 240, 1);\n box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 2px 0px;\n }\n\n .toggle.set:hover temba-icon {\n background: #fff;\n color: var(--color-primary-dark);\n }\n\n .circle .toggle.set temba-icon {\n margin-right: -70%;\n margin-bottom: -70%;\n }\n\n .hidden {\n display: none;\n }\n\n .controls temba-icon {\n margin: 0em 0.75em;\n background: rgba(255, 255, 255, 0.8);\n border-radius: 50%;\n padding: 6px;\n transition: all 0.1s ease-in-out;\n }\n\n .controls {\n pointer-events: none;\n display: flex;\n }\n\n .controls temba-icon {\n pointer-events: all;\n }\n\n .controls temba-icon.close {\n color: rgba(0, 0, 0, 0.2);\n background: rgba(255, 255, 255, 0.2);\n }\n\n .controls temba-icon.submit {\n color: rgba(0, 0, 0, 0.2);\n box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.1);\n }\n\n .controls temba-icon:hover {\n color: white;\n cursor: pointer;\n background: var(--color-primary-dark);\n }\n `;\n }\n\n @property({ type: String })\n tempImage: string;\n\n @property({ type: String })\n url: string;\n\n @property({ type: String })\n shape = 'square';\n\n @property({ type: Boolean, attribute: false })\n showCroppie = false;\n\n uploadReader = new FileReader();\n croppie: any;\n\n protected firstUpdated(\n changed: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.firstUpdated(changed);\n const picker = this;\n\n this.uploadReader.onload = function () {\n picker.launchCroppie(picker.uploadReader.result as any);\n };\n }\n\n public updated(changed: Map<string, any>): void {\n super.updated(changed);\n if (changed.has('url')) {\n this.setAttribute('url', this.url);\n }\n }\n\n private closeCroppie() {\n this.showCroppie = false;\n const wrapper = this.shadowRoot.querySelector('.croppie .embed');\n if (wrapper.firstChild) {\n wrapper.removeChild(wrapper.firstChild);\n }\n }\n\n private launchCroppie(url: string) {\n const wrapper = this.shadowRoot.querySelector('.croppie .embed');\n if (wrapper.firstChild) {\n wrapper.removeChild(wrapper.firstChild);\n }\n this.showCroppie = true;\n const ele = document.createElement('div');\n wrapper.appendChild(ele);\n\n const Croppie = (window as any).Croppie;\n this.croppie = new Croppie(ele, {\n enableExif: true,\n viewport: {\n width: 300,\n height: 300,\n type: this.shape\n },\n boundary: {\n width: 400,\n height: 400\n }\n });\n\n this.croppie.bind({ url });\n }\n\n private saveResult() {\n const picker = this;\n this.croppie\n .result({\n type: 'blob',\n size: 'viewport',\n format: 'webp',\n quality: 1,\n circle: false\n })\n .then(function (resp: any) {\n const blob = resp;\n picker.url = URL.createObjectURL(blob);\n\n // const blob = dataURItoBlob(resp);\n const fd = new FormData();\n fd.append(picker.name, blob, 'filename.webp');\n\n picker.value = fd;\n picker.closeCroppie();\n });\n }\n\n private handleToggleClicked() {\n const fileInput = this.shadowRoot.querySelector('#file');\n (fileInput as any).click();\n }\n\n private handleFileChanged(e: Event) {\n const input = e.target as any;\n if (input.files.length > 0) {\n this.uploadReader.readAsDataURL(input.files[0]);\n }\n input.value = '';\n }\n\n protected renderWidget() {\n return html`\n <div class=\"wrapper ${this.shape} ${this.label ? 'label' : ''}\">\n <input class='hidden' type=\"file\" accept=\"image/*\" capture=\"camera\" id=\"file\" name=\"file\" @change=${\n this.handleFileChanged\n }/>\n <div class='toggle ${this.url ? 'set' : ''} ${\n this.showCroppie ? 'hidden' : ''\n }' @click=${this.handleToggleClicked} style=\"background: ${\n this.url\n ? `url('${this.url}') center / contain no-repeat`\n : 'rgba(0, 0, 0, 0.1)'\n }\">\n <temba-icon name=${Icon.upload_image} size=\"1.5\"></temba-icon>\n </div>\n \n <temba-mask ?show=${this.showCroppie} class=\"${\n this.showCroppie ? 'editing' : ''\n }\">\n <div class='croppie'>\n <div class='embed'></div>\n <div class='controls'>\n <temba-icon class=\"close\" size=\"1\" name=${Icon.close} @click=${\n this.closeCroppie\n }></temba-icon>\n <div style=\"flex-grow:1\"></div>\n <temba-icon class=\"submit\" size=\"1\" name=${Icon.submit} @click=${\n this.saveResult\n }></temba-icon>\n </div>\n </temba-mask>\n </div>\n `;\n }\n\n public render() {\n return this.renderField();\n }\n}\n"]}
@@ -158,49 +158,53 @@ let KeyValueEditor = class KeyValueEditor extends BaseListEditor {
158
158
  getContainerClass() {
159
159
  return 'key-value-editor';
160
160
  }
161
- };
162
- KeyValueEditor.styles = css `
163
- .key-value-editor {
164
- display: flex;
165
- flex-direction: column;
166
- gap: 8px;
167
- }
161
+ static get styles() {
162
+ return css `
163
+ ${super.styles}
168
164
 
169
- .row {
170
- display: grid;
171
- grid-template-columns: 1fr 1fr auto;
172
- align-items: center;
173
- column-gap: 6px;
174
- }
165
+ .key-value-editor {
166
+ display: flex;
167
+ flex-direction: column;
168
+ gap: 8px;
169
+ }
175
170
 
176
- .remove-btn {
177
- width: 32px;
178
- height: 32px;
179
- border: 1px solid #ccc;
180
- border-radius: 4px;
181
- background: #f8f8f8;
182
- color: #666;
183
- cursor: pointer;
184
- display: flex;
185
- align-items: center;
186
- justify-content: center;
187
- font-size: 18px;
188
- }
171
+ .row {
172
+ display: grid;
173
+ grid-template-columns: 1fr 1fr auto;
174
+ align-items: center;
175
+ column-gap: 6px;
176
+ }
189
177
 
190
- .remove-btn:hover:not(:disabled) {
191
- background: #f0f0f0;
192
- }
178
+ .remove-btn {
179
+ width: 32px;
180
+ height: 32px;
181
+ border: 1px solid #ccc;
182
+ border-radius: 4px;
183
+ background: #f8f8f8;
184
+ color: #666;
185
+ cursor: pointer;
186
+ display: flex;
187
+ align-items: center;
188
+ justify-content: center;
189
+ font-size: 18px;
190
+ }
193
191
 
194
- .remove-btn:disabled {
195
- opacity: 0.5;
196
- cursor: not-allowed;
197
- }
192
+ .remove-btn:hover:not(:disabled) {
193
+ background: #f0f0f0;
194
+ }
195
+
196
+ .remove-btn:disabled {
197
+ opacity: 0.5;
198
+ cursor: not-allowed;
199
+ }
198
200
 
199
- .remove-btn-spacer {
200
- width: 32px;
201
- height: 32px;
201
+ .remove-btn-spacer {
202
+ width: 32px;
203
+ height: 32px;
204
+ }
205
+ `;
202
206
  }
203
- `;
207
+ };
204
208
  __decorate([
205
209
  property({ type: String })
206
210
  ], KeyValueEditor.prototype, "keyPlaceholder", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"KeyValueEditor.js","sourceRoot":"","sources":["../../../src/form/KeyValueEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, TemplateResult } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { BaseListEditor, ListItem } from './BaseListEditor';\n\ninterface KeyValueItem extends ListItem {\n key: string;\n value: string;\n}\n\n@customElement('temba-key-value-editor')\nexport class KeyValueEditor extends BaseListEditor<KeyValueItem> {\n @property({ type: String })\n keyPlaceholder = 'Key';\n\n @property({ type: String })\n valuePlaceholder = 'Value';\n\n @property({ type: Boolean })\n showValidation = true;\n\n @state()\n private keyErrors: { [index: number]: string } = {};\n\n // Configure to maintain empty items\n maintainEmptyItem = true;\n\n constructor() {\n super();\n this._items = [];\n }\n\n // External API uses array format to preserve duplicate keys\n @property({ type: Array })\n get value(): KeyValueItem[] {\n return this._items.filter(\n ({ key, value }) => key.trim() !== '' || value.trim() !== ''\n );\n }\n\n set value(newValue: KeyValueItem[] | Record<string, string>) {\n if (Array.isArray(newValue)) {\n this._items = [...newValue];\n } else {\n // Convert Record to array format\n this._items = Object.entries(newValue || {}).map(([key, value]) => ({\n key,\n value: typeof value === 'string' ? value : String(value)\n }));\n }\n this.requestUpdate();\n }\n\n // Implement abstract methods\n isEmptyItem(item: KeyValueItem): boolean {\n return item.key.trim() === '' && item.value.trim() === '';\n }\n\n createEmptyItem(): KeyValueItem {\n return { key: '', value: '' };\n }\n\n // Override cleanItems to return array format to preserve duplicate keys\n protected cleanItems(items: KeyValueItem[]): KeyValueItem[] {\n return items.filter(\n ({ key, value }) => key.trim() !== '' || value.trim() !== ''\n );\n }\n\n // Method to convert to Record format for final form submission\n toRecord(): Record<string, string> {\n const result: Record<string, string> = {};\n this._items.forEach(({ key, value }) => {\n if (key.trim() !== '' || value.trim() !== '') {\n result[key] = value;\n }\n });\n return result;\n }\n\n // Method to validate and set key errors for duplicates and empty keys with values\n validateKeys(): boolean {\n const newKeyErrors: { [index: number]: string } = {};\n\n // Check for empty keys with values\n this._items.forEach(({ key, value }, index) => {\n if (key.trim() === '' && value.trim() !== '') {\n newKeyErrors[index] = 'Key is required when value is provided';\n }\n });\n\n // Check for duplicate keys (only non-empty ones)\n const nonEmptyKeys = this._items\n .map(({ key }, index) => ({ key: key.trim(), index }))\n .filter(({ key }) => key !== '');\n\n const keyCount = new Map<string, number[]>();\n nonEmptyKeys.forEach(({ key, index }) => {\n if (!keyCount.has(key)) {\n keyCount.set(key, []);\n }\n keyCount.get(key)!.push(index);\n });\n\n // Mark duplicate keys with errors\n keyCount.forEach((indices, key) => {\n if (indices.length > 1) {\n indices.forEach((index) => {\n // Only show duplicate error if there's no empty key error already\n if (!newKeyErrors[index]) {\n newKeyErrors[index] = `Duplicate key \"${key}\"`;\n }\n });\n }\n });\n\n this.keyErrors = newKeyErrors;\n return Object.keys(newKeyErrors).length === 0;\n }\n\n // Clear key errors\n clearKeyErrors(): void {\n this.keyErrors = {};\n }\n\n // Override updateValue to emit array format and validate keys\n protected updateValue(newValue: KeyValueItem[]) {\n this._items = newValue;\n\n // Clear errors and re-validate when items change\n this.clearKeyErrors();\n this.validateKeys();\n\n this.dispatchEvent(\n new CustomEvent('change', {\n detail: { value: this.cleanItems(newValue) },\n bubbles: true\n })\n );\n this.requestUpdate();\n }\n\n private handleKeyChange(index: number, newKey: string) {\n const items = this.displayItems;\n const currentItem = items[index];\n\n // Clear any existing error for this key when it's modified\n if (this.keyErrors[index]) {\n const newKeyErrors = { ...this.keyErrors };\n delete newKeyErrors[index];\n this.keyErrors = newKeyErrors;\n }\n\n this.handleItemChange(index, {\n key: newKey,\n value: currentItem.value\n });\n }\n\n private handleValueChange(index: number, newValue: string) {\n const items = this.displayItems;\n const currentItem = items[index];\n\n // Clear any existing error for this key when value is modified\n if (this.keyErrors[index]) {\n const newKeyErrors = { ...this.keyErrors };\n delete newKeyErrors[index];\n this.keyErrors = newKeyErrors;\n }\n\n this.handleItemChange(index, {\n key: currentItem.key,\n value: newValue\n });\n }\n\n renderItem(item: KeyValueItem, index: number): TemplateResult {\n const canRemove = this.canRemoveItem(index);\n const keyError =\n this.showValidation && this.keyErrors[index] ? this.keyErrors[index] : '';\n\n return html`\n <div class=\"row\">\n <temba-textinput\n .value=${item.key}\n .placeholder=${this.keyPlaceholder}\n .errors=${keyError ? [keyError] : []}\n @change=${(e: any) => this.handleKeyChange(index, e.target.value)}\n ></temba-textinput>\n <temba-textinput\n .value=${item.value}\n .placeholder=${this.valuePlaceholder}\n @change=${(e: any) => this.handleValueChange(index, e.target.value)}\n ></temba-textinput>\n ${canRemove\n ? html`\n <button class=\"remove-btn\" @click=${() => this.removeItem(index)}>\n ×\n </button>\n `\n : html`<div class=\"remove-btn-spacer\"></div>`}\n </div>\n `;\n }\n\n protected getContainerClass(): string {\n return 'key-value-editor';\n }\n\n static styles = css`\n .key-value-editor {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n\n .row {\n display: grid;\n grid-template-columns: 1fr 1fr auto;\n align-items: center;\n column-gap: 6px;\n }\n\n .remove-btn {\n width: 32px;\n height: 32px;\n border: 1px solid #ccc;\n border-radius: 4px;\n background: #f8f8f8;\n color: #666;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n }\n\n .remove-btn:hover:not(:disabled) {\n background: #f0f0f0;\n }\n\n .remove-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .remove-btn-spacer {\n width: 32px;\n height: 32px;\n }\n `;\n}\n"]}
1
+ {"version":3,"file":"KeyValueEditor.js","sourceRoot":"","sources":["../../../src/form/KeyValueEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, TemplateResult } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { BaseListEditor, ListItem } from './BaseListEditor';\n\ninterface KeyValueItem extends ListItem {\n key: string;\n value: string;\n}\n\n@customElement('temba-key-value-editor')\nexport class KeyValueEditor extends BaseListEditor<KeyValueItem> {\n @property({ type: String })\n keyPlaceholder = 'Key';\n\n @property({ type: String })\n valuePlaceholder = 'Value';\n\n @property({ type: Boolean })\n showValidation = true;\n\n @state()\n private keyErrors: { [index: number]: string } = {};\n\n // Configure to maintain empty items\n maintainEmptyItem = true;\n\n constructor() {\n super();\n this._items = [];\n }\n\n // External API uses array format to preserve duplicate keys\n @property({ type: Array })\n get value(): KeyValueItem[] | any[] {\n return this._items.filter(\n ({ key, value }) => key.trim() !== '' || value.trim() !== ''\n );\n }\n\n set value(newValue: KeyValueItem[] | Record<string, string> | any) {\n if (Array.isArray(newValue)) {\n this._items = [...newValue];\n } else {\n // Convert Record to array format\n this._items = Object.entries(newValue || {}).map(([key, value]) => ({\n key,\n value: typeof value === 'string' ? value : String(value)\n }));\n }\n this.requestUpdate();\n }\n\n // Implement abstract methods\n isEmptyItem(item: KeyValueItem): boolean {\n return item.key.trim() === '' && item.value.trim() === '';\n }\n\n createEmptyItem(): KeyValueItem {\n return { key: '', value: '' };\n }\n\n // Override cleanItems to return array format to preserve duplicate keys\n protected cleanItems(items: KeyValueItem[]): KeyValueItem[] {\n return items.filter(\n ({ key, value }) => key.trim() !== '' || value.trim() !== ''\n );\n }\n\n // Method to convert to Record format for final form submission\n toRecord(): Record<string, string> {\n const result: Record<string, string> = {};\n this._items.forEach(({ key, value }) => {\n if (key.trim() !== '' || value.trim() !== '') {\n result[key] = value;\n }\n });\n return result;\n }\n\n // Method to validate and set key errors for duplicates and empty keys with values\n validateKeys(): boolean {\n const newKeyErrors: { [index: number]: string } = {};\n\n // Check for empty keys with values\n this._items.forEach(({ key, value }, index) => {\n if (key.trim() === '' && value.trim() !== '') {\n newKeyErrors[index] = 'Key is required when value is provided';\n }\n });\n\n // Check for duplicate keys (only non-empty ones)\n const nonEmptyKeys = this._items\n .map(({ key }, index) => ({ key: key.trim(), index }))\n .filter(({ key }) => key !== '');\n\n const keyCount = new Map<string, number[]>();\n nonEmptyKeys.forEach(({ key, index }) => {\n if (!keyCount.has(key)) {\n keyCount.set(key, []);\n }\n keyCount.get(key)!.push(index);\n });\n\n // Mark duplicate keys with errors\n keyCount.forEach((indices, key) => {\n if (indices.length > 1) {\n indices.forEach((index) => {\n // Only show duplicate error if there's no empty key error already\n if (!newKeyErrors[index]) {\n newKeyErrors[index] = `Duplicate key \"${key}\"`;\n }\n });\n }\n });\n\n this.keyErrors = newKeyErrors;\n return Object.keys(newKeyErrors).length === 0;\n }\n\n // Clear key errors\n clearKeyErrors(): void {\n this.keyErrors = {};\n }\n\n // Override updateValue to emit array format and validate keys\n protected updateValue(newValue: KeyValueItem[]) {\n this._items = newValue;\n\n // Clear errors and re-validate when items change\n this.clearKeyErrors();\n this.validateKeys();\n\n this.dispatchEvent(\n new CustomEvent('change', {\n detail: { value: this.cleanItems(newValue) },\n bubbles: true\n })\n );\n this.requestUpdate();\n }\n\n private handleKeyChange(index: number, newKey: string) {\n const items = this.displayItems;\n const currentItem = items[index];\n\n // Clear any existing error for this key when it's modified\n if (this.keyErrors[index]) {\n const newKeyErrors = { ...this.keyErrors };\n delete newKeyErrors[index];\n this.keyErrors = newKeyErrors;\n }\n\n this.handleItemChange(index, {\n key: newKey,\n value: currentItem.value\n });\n }\n\n private handleValueChange(index: number, newValue: string) {\n const items = this.displayItems;\n const currentItem = items[index];\n\n // Clear any existing error for this key when value is modified\n if (this.keyErrors[index]) {\n const newKeyErrors = { ...this.keyErrors };\n delete newKeyErrors[index];\n this.keyErrors = newKeyErrors;\n }\n\n this.handleItemChange(index, {\n key: currentItem.key,\n value: newValue\n });\n }\n\n renderItem(item: KeyValueItem, index: number): TemplateResult {\n const canRemove = this.canRemoveItem(index);\n const keyError =\n this.showValidation && this.keyErrors[index] ? this.keyErrors[index] : '';\n\n return html`\n <div class=\"row\">\n <temba-textinput\n .value=${item.key}\n .placeholder=${this.keyPlaceholder}\n .errors=${keyError ? [keyError] : []}\n @change=${(e: any) => this.handleKeyChange(index, e.target.value)}\n ></temba-textinput>\n <temba-textinput\n .value=${item.value}\n .placeholder=${this.valuePlaceholder}\n @change=${(e: any) => this.handleValueChange(index, e.target.value)}\n ></temba-textinput>\n ${canRemove\n ? html`\n <button class=\"remove-btn\" @click=${() => this.removeItem(index)}>\n ×\n </button>\n `\n : html`<div class=\"remove-btn-spacer\"></div>`}\n </div>\n `;\n }\n\n protected getContainerClass(): string {\n return 'key-value-editor';\n }\n\n static get styles() {\n return css`\n ${super.styles}\n\n .key-value-editor {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n\n .row {\n display: grid;\n grid-template-columns: 1fr 1fr auto;\n align-items: center;\n column-gap: 6px;\n }\n\n .remove-btn {\n width: 32px;\n height: 32px;\n border: 1px solid #ccc;\n border-radius: 4px;\n background: #f8f8f8;\n color: #666;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n }\n\n .remove-btn:hover:not(:disabled) {\n background: #f0f0f0;\n }\n\n .remove-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .remove-btn-spacer {\n width: 32px;\n height: 32px;\n }\n `;\n }\n}\n"]}
@@ -2,18 +2,17 @@ import { __decorate } from "tslib";
2
2
  import { css, html } from 'lit';
3
3
  import { property } from 'lit/decorators.js';
4
4
  import { ifDefined } from 'lit-html/directives/if-defined.js';
5
- import { FormElement } from './FormElement';
5
+ import { FieldElement } from './FieldElement';
6
6
  import { getClasses } from '../utils';
7
7
  import { Icon } from '../Icons';
8
8
  /**
9
9
  * MessageEditor is a composed component that combines temba-completion and temba-media-picker
10
10
  * for editing messages with text completion and file attachments
11
11
  */
12
- export class MessageEditor extends FormElement {
12
+ export class MessageEditor extends FieldElement {
13
13
  constructor() {
14
14
  super(...arguments);
15
15
  this.name = '';
16
- this.value = '';
17
16
  this.placeholder = '';
18
17
  this.textarea = true;
19
18
  this.autogrow = true;
@@ -28,6 +27,7 @@ export class MessageEditor extends FormElement {
28
27
  }
29
28
  static get styles() {
30
29
  return css `
30
+ ${super.styles}
31
31
  :host {
32
32
  display: block;
33
33
  }
@@ -292,85 +292,77 @@ export class MessageEditor extends FormElement {
292
292
  }
293
293
  }
294
294
  render() {
295
+ return this.renderField();
296
+ }
297
+ renderWidget() {
295
298
  const hasAttachments = this.hasStaticAttachments();
296
299
  return html `
297
- <temba-field
298
- name=${this.name}
299
- .label=${this.label}
300
- .helpText=${this.helpText}
301
- .errors=${this.errors}
302
- .widgetOnly=${this.widgetOnly}
303
- >
304
- <div
305
- class=${getClasses({
300
+ <div
301
+ class=${getClasses({
306
302
  'message-editor-container': true,
307
303
  highlight: this.pendingDrop,
308
304
  'has-attachments': hasAttachments
309
305
  })}
310
- @dragenter=${this.handleDragEnter}
311
- @dragover=${this.handleDragOver}
312
- @dragleave=${this.handleDragLeave}
313
- @drop=${this.handleDrop}
314
- >
315
- <div class="completion-wrapper">
316
- <temba-completion
317
- name=${this.name}
318
- .value=${this.value}
319
- placeholder=${this.placeholder}
320
- ?textarea=${this.textarea}
321
- ?autogrow=${this.autogrow}
322
- ?session=${this.session}
323
- ?submitOnEnter=${this.submitOnEnter}
324
- ?gsm=${this.gsm}
325
- ?disableCompletion=${this.disableCompletion}
326
- maxlength=${ifDefined(this.maxLength)}
327
- counter=${ifDefined(this.counter)}
328
- minHeight=${ifDefined(this.minHeight)}
329
- widgetOnly
330
- @change=${this.handleCompletionChange}
331
- ></temba-completion>
332
- </div>
306
+ @dragenter=${this.handleDragEnter}
307
+ @dragover=${this.handleDragOver}
308
+ @dragleave=${this.handleDragLeave}
309
+ @drop=${this.handleDrop}
310
+ >
311
+ <div class="completion-wrapper">
312
+ <temba-completion
313
+ name=${this.name}
314
+ .value=${this.value}
315
+ placeholder=${this.placeholder}
316
+ ?textarea=${this.textarea}
317
+ ?autogrow=${this.autogrow}
318
+ ?session=${this.session}
319
+ ?submitOnEnter=${this.submitOnEnter}
320
+ ?gsm=${this.gsm}
321
+ ?disableCompletion=${this.disableCompletion}
322
+ maxlength=${ifDefined(this.maxLength)}
323
+ counter=${ifDefined(this.counter)}
324
+ minHeight=${ifDefined(this.minHeight)}
325
+ widgetOnly
326
+ @change=${this.handleCompletionChange}
327
+ ></temba-completion>
328
+ </div>
329
+
330
+ <div class="media-wrapper ">
331
+ <temba-media-picker
332
+ .accept=${this.accept}
333
+ .max=${this.maxAttachments}
334
+ .endpoint=${this.endpoint}
335
+ @change=${this.handleMediaChange}
336
+ ignoreDrops
337
+ ></temba-media-picker>
338
+ </div>
339
+ <temba-icon
340
+ class="attachment-icon"
341
+ name=${Icon.attachment}
342
+ size="1.2"
343
+ @click=${this.handleAttachmentIconClick}
344
+ ></temba-icon>
345
+
346
+ <div class="drop-overlay"></div>
333
347
 
334
- <div class="media-wrapper ">
335
- <temba-media-picker
348
+ <!-- Hidden media picker for handling uploads when no attachments are shown -->
349
+ ${!hasAttachments
350
+ ? html `<temba-media-picker
351
+ style="display: none;"
336
352
  .accept=${this.accept}
337
353
  .max=${this.maxAttachments}
338
354
  .endpoint=${this.endpoint}
339
355
  @change=${this.handleMediaChange}
340
356
  ignoreDrops
341
- ></temba-media-picker>
342
- </div>
343
- <temba-icon
344
- class="attachment-icon"
345
- name=${Icon.attachment}
346
- size="1.2"
347
- @click=${this.handleAttachmentIconClick}
348
- ></temba-icon>
349
-
350
- <div class="drop-overlay"></div>
351
-
352
- <!-- Hidden media picker for handling uploads when no attachments are shown -->
353
- ${!hasAttachments
354
- ? html `<temba-media-picker
355
- style="display: none;"
356
- .accept=${this.accept}
357
- .max=${this.maxAttachments}
358
- .endpoint=${this.endpoint}
359
- @change=${this.handleMediaChange}
360
- ignoreDrops
361
- ></temba-media-picker>`
357
+ ></temba-media-picker>`
362
358
  : ''}
363
- </div>
364
- </temba-field>
359
+ </div>
365
360
  `;
366
361
  }
367
362
  }
368
363
  __decorate([
369
364
  property({ type: String })
370
365
  ], MessageEditor.prototype, "name", void 0);
371
- __decorate([
372
- property({ type: String })
373
- ], MessageEditor.prototype, "value", void 0);
374
366
  __decorate([
375
367
  property({ type: String })
376
368
  ], MessageEditor.prototype, "placeholder", void 0);