@nyaruka/temba-components 0.130.1 → 0.130.2

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 (246) hide show
  1. package/CHANGELOG.md +28 -4
  2. package/DEV_DATA.md +89 -0
  3. package/demo/data/flows/food-order.json +4 -4
  4. package/demo/data/flows/sample-flow.json +132 -147
  5. package/dist/temba-components.js +764 -628
  6. package/dist/temba-components.js.map +1 -1
  7. package/out-tsc/src/display/Chat.js +5 -3
  8. package/out-tsc/src/display/Chat.js.map +1 -1
  9. package/out-tsc/src/events.js.map +1 -1
  10. package/out-tsc/src/flow/CanvasNode.js +83 -78
  11. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  12. package/out-tsc/src/flow/Editor.js +1 -0
  13. package/out-tsc/src/flow/Editor.js.map +1 -1
  14. package/out-tsc/src/flow/NodeEditor.js +47 -3
  15. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  16. package/out-tsc/src/flow/actions/add_contact_urn.js +1 -1
  17. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
  18. package/out-tsc/src/flow/actions/set_contact_channel.js +1 -1
  19. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  20. package/out-tsc/src/flow/actions/set_contact_field.js +2 -1
  21. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  22. package/out-tsc/src/flow/actions/set_contact_language.js +3 -1
  23. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  24. package/out-tsc/src/flow/actions/set_contact_name.js +1 -1
  25. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
  26. package/out-tsc/src/flow/actions/set_contact_status.js +17 -14
  27. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  28. package/out-tsc/src/flow/actions/set_run_result.js +1 -1
  29. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  30. package/out-tsc/src/flow/nodes/split_by_llm.js +12 -12
  31. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
  32. package/out-tsc/src/flow/nodes/wait_for_response.js +609 -6
  33. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  34. package/out-tsc/src/flow/operators.js +194 -0
  35. package/out-tsc/src/flow/operators.js.map +1 -0
  36. package/out-tsc/src/flow/types.js.map +1 -1
  37. package/out-tsc/src/form/ArrayEditor.js +84 -19
  38. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  39. package/out-tsc/src/form/Checkbox.js +12 -0
  40. package/out-tsc/src/form/Checkbox.js.map +1 -1
  41. package/out-tsc/src/form/FieldRenderer.js +13 -3
  42. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  43. package/out-tsc/src/form/TextInput.js +20 -1
  44. package/out-tsc/src/form/TextInput.js.map +1 -1
  45. package/out-tsc/src/form/select/Select.js +7 -0
  46. package/out-tsc/src/form/select/Select.js.map +1 -1
  47. package/out-tsc/src/interfaces.js.map +1 -1
  48. package/out-tsc/src/layout/Dialog.js +3 -4
  49. package/out-tsc/src/layout/Dialog.js.map +1 -1
  50. package/out-tsc/src/list/RunList.js +2 -2
  51. package/out-tsc/src/list/RunList.js.map +1 -1
  52. package/out-tsc/src/live/ContactChat.js +114 -34
  53. package/out-tsc/src/live/ContactChat.js.map +1 -1
  54. package/out-tsc/src/live/ContactDetails.js +7 -0
  55. package/out-tsc/src/live/ContactDetails.js.map +1 -1
  56. package/out-tsc/src/live/ContactNameFetch.js +1 -1
  57. package/out-tsc/src/live/ContactNameFetch.js.map +1 -1
  58. package/out-tsc/test/NodeHelper.js +25 -27
  59. package/out-tsc/test/NodeHelper.js.map +1 -1
  60. package/out-tsc/test/nodes/split_by_llm.test.js +12 -4
  61. package/out-tsc/test/nodes/split_by_llm.test.js.map +1 -1
  62. package/out-tsc/test/nodes/split_by_llm_categorize.test.js +101 -91
  63. package/out-tsc/test/nodes/split_by_llm_categorize.test.js.map +1 -1
  64. package/out-tsc/test/nodes/split_by_random.test.js +120 -112
  65. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
  66. package/out-tsc/test/nodes/wait_for_digits.test.js +131 -111
  67. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  68. package/out-tsc/test/nodes/wait_for_response.test.js +549 -85
  69. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
  70. package/out-tsc/test/temba-checkbox.test.js +32 -32
  71. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  72. package/out-tsc/test/temba-contact-chat.test.js +2 -1
  73. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  74. package/out-tsc/test/temba-dropdown.test.js +0 -4
  75. package/out-tsc/test/temba-dropdown.test.js.map +1 -1
  76. package/out-tsc/test/temba-flow-editor-node.test.js +9 -4
  77. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  78. package/out-tsc/test/temba-integration-markdown.test.js +13 -15
  79. package/out-tsc/test/temba-integration-markdown.test.js.map +1 -1
  80. package/out-tsc/test/temba-node-editor.test.js +5 -38
  81. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  82. package/out-tsc/test/temba-run-list.test.js +2 -2
  83. package/out-tsc/test/temba-run-list.test.js.map +1 -1
  84. package/out-tsc/test/utils.test.js +2 -1
  85. package/out-tsc/test/utils.test.js.map +1 -1
  86. package/package.json +6 -2
  87. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  88. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  89. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  90. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  91. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  92. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  93. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  94. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  95. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  96. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  97. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  98. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  99. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  100. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  101. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  102. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  103. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  104. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  105. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  106. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  107. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  108. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  109. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  110. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  111. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  112. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  113. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  114. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  115. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  116. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  117. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  118. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  119. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  120. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  121. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  122. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  123. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  124. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  125. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  126. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  127. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  128. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  129. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  130. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  131. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  132. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  133. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  134. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  135. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  136. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  137. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  138. package/screenshots/truth/actions/send_msg/render/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-whitespace-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/editor/wait.png +0 -0
  145. package/screenshots/truth/integration/textinput-markdown-errors.png +0 -0
  146. package/screenshots/truth/lightbox/img-zoomed.png +0 -0
  147. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  148. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  149. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  150. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  151. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  152. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  153. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  154. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  155. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  156. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  157. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  158. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  159. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  160. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  161. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  162. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  163. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  164. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  165. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  166. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  167. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  168. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  169. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  170. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  171. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  172. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  173. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  174. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  175. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  176. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  177. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  178. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  179. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  180. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  181. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  182. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  183. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  184. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  185. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  186. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  187. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  188. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  189. package/screenshots/truth/run-list/basic.png +0 -0
  190. package/screenshots/truth/templates/default.png +0 -0
  191. package/screenshots/truth/wait-for-response/rules-editor.png +0 -0
  192. package/screenshots/truth/wait-for-response/timeout-editor-unchecked.png +0 -0
  193. package/screenshots/truth/wait-for-response/timeout-editor.png +0 -0
  194. package/scripts/dev-data-sync.mjs +182 -0
  195. package/src/display/Chat.ts +6 -4
  196. package/src/events.ts +6 -5
  197. package/src/flow/CanvasNode.ts +89 -79
  198. package/src/flow/Editor.ts +1 -0
  199. package/src/flow/NodeEditor.ts +55 -3
  200. package/src/flow/actions/add_contact_urn.ts +1 -1
  201. package/src/flow/actions/set_contact_channel.ts +1 -1
  202. package/src/flow/actions/set_contact_field.ts +2 -1
  203. package/src/flow/actions/set_contact_language.ts +3 -1
  204. package/src/flow/actions/set_contact_name.ts +1 -1
  205. package/src/flow/actions/set_contact_status.ts +18 -18
  206. package/src/flow/actions/set_run_result.ts +1 -1
  207. package/src/flow/nodes/split_by_llm.ts +14 -13
  208. package/src/flow/nodes/wait_for_response.ts +717 -5
  209. package/src/flow/operators.ts +215 -0
  210. package/src/flow/types.ts +10 -2
  211. package/src/form/ArrayEditor.ts +117 -37
  212. package/src/form/Checkbox.ts +12 -0
  213. package/src/form/FieldRenderer.ts +24 -3
  214. package/src/form/TextInput.ts +19 -1
  215. package/src/form/select/Select.ts +7 -0
  216. package/src/interfaces.ts +1 -1
  217. package/src/layout/Dialog.ts +4 -4
  218. package/src/list/RunList.ts +2 -2
  219. package/src/live/ContactChat.ts +144 -58
  220. package/src/live/ContactDetails.ts +7 -0
  221. package/src/live/ContactNameFetch.ts +1 -1
  222. package/static/api/labels.json +6 -1
  223. package/test/NodeHelper.ts +38 -40
  224. package/test/nodes/split_by_llm.test.ts +43 -32
  225. package/test/nodes/split_by_llm_categorize.test.ts +130 -120
  226. package/test/nodes/split_by_random.test.ts +136 -128
  227. package/test/nodes/wait_for_digits.test.ts +147 -127
  228. package/test/nodes/wait_for_response.test.ts +657 -104
  229. package/test/temba-checkbox.test.ts +36 -32
  230. package/test/temba-contact-chat.test.ts +2 -1
  231. package/test/temba-dropdown.test.ts +0 -12
  232. package/test/temba-flow-editor-node.test.ts +11 -4
  233. package/test/temba-integration-markdown.test.ts +16 -17
  234. package/test/temba-node-editor.test.ts +5 -43
  235. package/test/temba-run-list.test.ts +2 -2
  236. package/test/utils.test.ts +2 -1
  237. package/test-assets/list/runs.json +8 -8
  238. package/web-dev-mock.mjs +86 -30
  239. package/web-dev-server.config.mjs +272 -31
  240. package/screenshots/truth/dropdown/bottom-edge-collision.png +0 -0
  241. package/screenshots/truth/dropdown/right-edge-collision.png +0 -0
  242. package/screenshots/truth/editor/send_msg.png +0 -0
  243. package/screenshots/truth/editor/set_contact_language.png +0 -0
  244. package/screenshots/truth/editor/set_contact_name.png +0 -0
  245. package/screenshots/truth/editor/set_run_result.png +0 -0
  246. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
@@ -0,0 +1,215 @@
1
+ // Flow router operator configurations
2
+ // These define the available operators for rule-based routing in flow nodes
3
+
4
+ export interface OperatorConfig {
5
+ type: string;
6
+ name: string;
7
+ operands: number; // Number of operands required (0 = no input needed, 1 = single value, 2 = two values)
8
+ categoryName?: string; // Default category name when operands is 0
9
+ visibility?: 'hidden' | 'visible'; // Whether to show in UI
10
+ filter?: string; // Feature filter requirement
11
+ }
12
+
13
+ // All available operators for flow routing
14
+ export const OPERATORS: OperatorConfig[] = [
15
+ // Text operators
16
+ {
17
+ type: 'has_any_word',
18
+ name: 'has any of the words',
19
+ operands: 1
20
+ },
21
+ {
22
+ type: 'has_all_words',
23
+ name: 'has all of the words',
24
+ operands: 1
25
+ },
26
+ {
27
+ type: 'has_phrase',
28
+ name: 'has the phrase',
29
+ operands: 1
30
+ },
31
+ {
32
+ type: 'has_only_phrase',
33
+ name: 'has only the phrase',
34
+ operands: 1
35
+ },
36
+ {
37
+ type: 'has_beginning',
38
+ name: 'starts with',
39
+ operands: 1
40
+ },
41
+ {
42
+ type: 'has_text',
43
+ name: 'has some text',
44
+ operands: 0,
45
+ categoryName: 'Has Text'
46
+ },
47
+ {
48
+ type: 'has_pattern',
49
+ name: 'matches regex',
50
+ operands: 1
51
+ },
52
+
53
+ // Number operators
54
+ {
55
+ type: 'has_number',
56
+ name: 'has a number',
57
+ operands: 0,
58
+ categoryName: 'Has Number'
59
+ },
60
+ {
61
+ type: 'has_number_between',
62
+ name: 'has a number between',
63
+ operands: 2
64
+ },
65
+ {
66
+ type: 'has_number_lt',
67
+ name: 'has a number below',
68
+ operands: 1
69
+ },
70
+ {
71
+ type: 'has_number_lte',
72
+ name: 'has a number at or below',
73
+ operands: 1
74
+ },
75
+ {
76
+ type: 'has_number_eq',
77
+ name: 'has a number equal to',
78
+ operands: 1
79
+ },
80
+ {
81
+ type: 'has_number_gte',
82
+ name: 'has a number at or above',
83
+ operands: 1
84
+ },
85
+ {
86
+ type: 'has_number_gt',
87
+ name: 'has a number above',
88
+ operands: 1
89
+ },
90
+
91
+ // Date operators
92
+ {
93
+ type: 'has_date',
94
+ name: 'has a date',
95
+ operands: 0,
96
+ categoryName: 'Has Date'
97
+ },
98
+ {
99
+ type: 'has_date_lt',
100
+ name: 'has a date before',
101
+ operands: 1
102
+ },
103
+ {
104
+ type: 'has_date_eq',
105
+ name: 'has a date equal to',
106
+ operands: 1
107
+ },
108
+ {
109
+ type: 'has_date_gt',
110
+ name: 'has a date after',
111
+ operands: 1
112
+ },
113
+ {
114
+ type: 'has_time',
115
+ name: 'has a time',
116
+ operands: 0,
117
+ categoryName: 'Has Time'
118
+ },
119
+
120
+ // Contact data operators
121
+ {
122
+ type: 'has_phone',
123
+ name: 'has a phone number',
124
+ operands: 0,
125
+ categoryName: 'Has Phone'
126
+ },
127
+ {
128
+ type: 'has_email',
129
+ name: 'has an email',
130
+ operands: 0,
131
+ categoryName: 'Has Email'
132
+ },
133
+
134
+ // Location operators (require location feature)
135
+ {
136
+ type: 'has_state',
137
+ name: 'has state',
138
+ operands: 0,
139
+ categoryName: 'Has State',
140
+ filter: 'HAS_LOCATIONS'
141
+ },
142
+ {
143
+ type: 'has_district',
144
+ name: 'has district',
145
+ operands: 1,
146
+ categoryName: 'Has District',
147
+ filter: 'HAS_LOCATIONS'
148
+ },
149
+ {
150
+ type: 'has_ward',
151
+ name: 'has ward',
152
+ operands: 2,
153
+ categoryName: 'Has Ward',
154
+ filter: 'HAS_LOCATIONS'
155
+ },
156
+
157
+ // Hidden/system operators
158
+ {
159
+ type: 'has_group',
160
+ name: 'is in the group',
161
+ operands: 1,
162
+ visibility: 'hidden'
163
+ },
164
+ {
165
+ type: 'has_category',
166
+ name: 'has the category',
167
+ operands: 0,
168
+ visibility: 'hidden'
169
+ },
170
+ {
171
+ type: 'has_error',
172
+ name: 'has an error',
173
+ operands: 0,
174
+ categoryName: 'Has Error',
175
+ visibility: 'hidden'
176
+ },
177
+ {
178
+ type: 'has_value',
179
+ name: 'is not empty',
180
+ operands: 0,
181
+ categoryName: 'Not Empty',
182
+ visibility: 'hidden'
183
+ }
184
+ ];
185
+
186
+ // Get operators suitable for wait_for_response rules
187
+ export const getWaitForResponseOperators = (): OperatorConfig[] => {
188
+ return OPERATORS.filter(
189
+ (op) => op.visibility !== 'hidden' && !op.filter // For now, exclude location operators unless we support feature detection
190
+ );
191
+ };
192
+
193
+ // Get operator configuration by type
194
+ export const getOperatorConfig = (type: string): OperatorConfig | undefined => {
195
+ return OPERATORS.find((op) => op.type === type);
196
+ };
197
+
198
+ // Convert operators to select options
199
+ export const operatorsToSelectOptions = (operators: OperatorConfig[]) => {
200
+ return operators.map((op) => ({
201
+ value: op.type,
202
+ name: op.name
203
+ }));
204
+ };
205
+
206
+ // Create an operator object for select components
207
+ export const createOperatorOption = (
208
+ type: string
209
+ ): { value: string; name: string } => {
210
+ const config = getOperatorConfig(type);
211
+ return {
212
+ value: type,
213
+ name: config ? config.name : type
214
+ };
215
+ };
package/src/flow/types.ts CHANGED
@@ -70,6 +70,7 @@ export interface FormData extends Record<string, any> {}
70
70
  export interface FormConfig {
71
71
  form?: Record<string, FieldConfig>;
72
72
  layout?: LayoutItem[];
73
+ gutter?: LayoutItem[];
73
74
  sanitize?: (formData: FormData) => void;
74
75
  validate?: (formData: FormData) => ValidationResult;
75
76
  }
@@ -78,6 +79,7 @@ export interface NodeConfig extends FormConfig {
78
79
  type: string;
79
80
  name?: string;
80
81
  color?: string;
82
+ dialogSize?: 'small' | 'medium' | 'large' | 'xlarge';
81
83
  action?: ActionConfig;
82
84
  router?: {
83
85
  type: 'switch' | 'random';
@@ -103,7 +105,7 @@ export interface NodeConfig extends FormConfig {
103
105
 
104
106
  // New field configuration system for generic form generation
105
107
  export interface BaseFieldConfig {
106
- label?: string;
108
+ label?: string | ((formData: Record<string, any>) => string);
107
109
  required?: boolean;
108
110
  evaluated?: boolean;
109
111
  dependsOn?: string[];
@@ -121,6 +123,7 @@ export interface BaseFieldConfig {
121
123
 
122
124
  // Layout properties
123
125
  maxWidth?: string;
126
+ width?: string;
124
127
 
125
128
  // Conditional rendering
126
129
  conditions?: {
@@ -132,6 +135,7 @@ export interface BaseFieldConfig {
132
135
  export interface TextFieldConfig extends BaseFieldConfig {
133
136
  type: 'text';
134
137
  placeholder?: string;
138
+ flavor?: 'xsmall' | 'small' | 'large';
135
139
  }
136
140
 
137
141
  export interface TextareaFieldConfig extends BaseFieldConfig {
@@ -155,7 +159,7 @@ export interface SelectFieldConfig extends BaseFieldConfig {
155
159
  endpoint?: string;
156
160
  emails?: boolean;
157
161
  getName?: (item: any) => string;
158
- flavor?: 'small' | 'large';
162
+ flavor?: 'xsmall' | 'small' | 'large';
159
163
  createArbitraryOption?: (input: string, options: any[]) => any;
160
164
  allowCreate?: boolean;
161
165
  getDynamicOptions?: () => Array<{ value: string; name: string }>;
@@ -176,6 +180,7 @@ export interface ArrayFieldConfig extends BaseFieldConfig {
176
180
  minItems?: number;
177
181
  maxItems?: number;
178
182
  itemLabel?: string;
183
+ maintainEmptyItem?: boolean;
179
184
  onItemChange?: (
180
185
  itemIndex: number,
181
186
  field: string,
@@ -189,6 +194,7 @@ export interface CheckboxFieldConfig extends BaseFieldConfig {
189
194
  type: 'checkbox';
190
195
  size?: number;
191
196
  animateChange?: string;
197
+ labelPadding?: string;
192
198
  }
193
199
 
194
200
  export interface MessageEditorFieldConfig extends BaseFieldConfig {
@@ -246,11 +252,13 @@ export type LayoutItem =
246
252
  export interface ActionConfig extends FormConfig {
247
253
  name: string;
248
254
  color: string;
255
+ dialogSize?: 'small' | 'medium' | 'large' | 'xlarge';
249
256
  evaluated?: string[];
250
257
  render?: (node: any, action: any) => TemplateResult;
251
258
 
252
259
  form?: Record<string, FieldConfig>;
253
260
  layout?: LayoutItem[]; // optional layout configuration - array of layout items
261
+ gutter?: LayoutItem[]; // fields to render in the dialog gutter (left side of buttons)
254
262
 
255
263
  toFormData?: (action: Action) => any;
256
264
  fromFormData?: (formData: any) => Action;
@@ -134,52 +134,115 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
134
134
  ): TemplateResult {
135
135
  const computedValue = this.computeFieldValue(itemIndex, fieldName, config);
136
136
 
137
+ // Extract flavor from select config if available
138
+ const flavor =
139
+ config.type === 'select' ? (config as any).flavor || 'small' : 'small';
140
+
141
+ // Build container style with width/maxWidth if specified
142
+ let containerStyle = '';
143
+ if (config.width) {
144
+ containerStyle = `width: ${config.width};`;
145
+ } else if (config.maxWidth) {
146
+ containerStyle = `max-width: ${config.maxWidth};`;
147
+ }
148
+
137
149
  // Use FieldRenderer for consistent field rendering
138
- return FieldRenderer.renderField(fieldName, config, computedValue, {
139
- showLabel: false, // ArrayEditor doesn't show labels for individual fields
140
- flavor: 'small', // ArrayEditor uses small flavor
141
- extraClasses: 'form-control',
142
- onChange: (e: Event) => {
143
- let value: any;
144
- const target = e.target as any;
145
-
146
- // Handle different field types and their change events
147
- if (config.type === 'select') {
148
- // Use consistent temba-select value normalization
149
- value = target.values;
150
- } else {
151
- // For other field types, use the target value directly
152
- value = target.value;
150
+ const fieldContent = FieldRenderer.renderField(
151
+ fieldName,
152
+ config,
153
+ computedValue,
154
+ {
155
+ showLabel: false, // ArrayEditor doesn't show labels for individual fields
156
+ flavor: flavor,
157
+ extraClasses: 'form-control',
158
+ onChange: (e: Event) => {
159
+ let value: any;
160
+ const target = e.target as any;
161
+
162
+ // Handle different field types and their change events
163
+ if (config.type === 'select') {
164
+ // Use consistent temba-select value normalization
165
+ value = target.values;
166
+ } else {
167
+ // For other field types, use the target value directly
168
+ value = target.value;
169
+ }
170
+
171
+ this.handleFieldChange(itemIndex, fieldName, value);
153
172
  }
154
-
155
- this.handleFieldChange(itemIndex, fieldName, value);
156
173
  }
157
- });
174
+ );
175
+
176
+ // Wrap in container with style if maxWidth is specified
177
+ if (containerStyle) {
178
+ return html`<div style="${containerStyle}">${fieldContent}</div>`;
179
+ }
180
+
181
+ return fieldContent;
158
182
  }
159
183
 
160
184
  renderItem(item: ListItem, index: number): TemplateResult {
161
185
  const canRemove = this.canRemoveItem(index);
162
186
 
187
+ // Render fields and track if any value fields are visible
188
+ const fieldElements: TemplateResult[] = [];
189
+ let hasVisibleValueField = false;
190
+
191
+ Object.entries(this.itemConfig).forEach(([fieldName, config]) => {
192
+ // Check visibility condition
193
+ let isVisible = true;
194
+ if (config.conditions?.visible) {
195
+ try {
196
+ const currentItem = this._items[index] || {};
197
+ isVisible = config.conditions.visible(currentItem);
198
+ } catch (error) {
199
+ console.error(`Error checking visibility for ${fieldName}:`, error);
200
+ }
201
+ }
202
+
203
+ if (isVisible) {
204
+ // Check if this is a value field (text input without fixed sizing)
205
+ const isValueField =
206
+ !config.width && !config.maxWidth && config.type === 'text';
207
+ if (isValueField) {
208
+ hasVisibleValueField = true;
209
+ }
210
+
211
+ fieldElements.push(html`
212
+ <div
213
+ class="field ${config.width ||
214
+ config.maxWidth ||
215
+ config.type === 'select'
216
+ ? 'field-fixed'
217
+ : 'field-flex'}"
218
+ >
219
+ ${this.renderArrayField(index, fieldName, config)}
220
+ </div>
221
+ `);
222
+ }
223
+ });
224
+
225
+ // If no value fields are visible, add a spacer to maintain alignment
226
+ if (!hasVisibleValueField) {
227
+ // Insert spacer after operator (first field) and before category (last field)
228
+ fieldElements.splice(
229
+ -1,
230
+ 0,
231
+ html`<div class="field field-flex spacer"></div>`
232
+ );
233
+ }
234
+
163
235
  return html`
164
236
  <div class="array-item">
165
237
  <div class="item-fields">
166
- ${Object.entries(this.itemConfig).map(
167
- ([fieldName, config]) => html`
168
- <div class="field">
169
- ${this.renderArrayField(index, fieldName, config)}
170
- </div>
171
- `
172
- )}
173
- ${canRemove
174
- ? html`
175
- <button
176
- @click=${() => this.removeItem(index)}
177
- class="remove-btn"
178
- >
179
- <temba-icon name="x"></temba-icon>
180
- </button>
181
- `
182
- : ''}
238
+ ${fieldElements}
239
+ <button
240
+ @click=${canRemove ? () => this.removeItem(index) : undefined}
241
+ class="remove-btn ${canRemove ? '' : 'invisible'}"
242
+ ?disabled=${!canRemove}
243
+ >
244
+ <temba-icon name="x"></temba-icon>
245
+ </button>
183
246
  </div>
184
247
  </div>
185
248
  `;
@@ -217,12 +280,24 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
217
280
  }
218
281
 
219
282
  .field {
220
- flex: 1;
283
+ /* Base field styles */
284
+ }
285
+
286
+ .field-flex {
287
+ flex: 1; /* Grow to fill remaining space */
288
+ }
289
+
290
+ .field-fixed {
291
+ flex: none; /* Don't grow, use content/maxWidth size */
292
+ }
293
+
294
+ .spacer {
295
+ /* Empty spacer to maintain layout alignment */
221
296
  }
222
297
 
223
298
  .add-btn,
224
299
  .remove-btn {
225
- padding: 8px;
300
+ padding: 4px;
226
301
  border: 1px solid #ccc;
227
302
  border-radius: 4px;
228
303
  background: white;
@@ -239,6 +314,11 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
239
314
  background: #fefefe;
240
315
  color: #999;
241
316
  }
317
+
318
+ .remove-btn.invisible {
319
+ visibility: hidden;
320
+ cursor: default;
321
+ }
242
322
  `;
243
323
  }
244
324
  }
@@ -18,6 +18,16 @@ export class Checkbox extends FieldElement {
18
18
  width: 100%;
19
19
  }
20
20
 
21
+ .checkbox-background {
22
+ position: absolute;
23
+ margin-top: 2px;
24
+ margin-left: 2px;
25
+ width: 12px;
26
+ height: 12px;
27
+ background: var(--checkbox-background, rgba(255, 255, 255, 0.8));
28
+ border-radius: 2px;
29
+ }
30
+
21
31
  .wrapper.label {
22
32
  padding: var(--checkbox-padding, 10px);
23
33
  border-radius: var(--curvature);
@@ -163,7 +173,9 @@ export class Checkbox extends FieldElement {
163
173
  @click=${this.handleClick}
164
174
  >
165
175
  <div class="checkbox-container ${this.disabled ? 'disabled' : ''}">
176
+ <div class="checkbox-background"></div>
166
177
  ${icon}
178
+
167
179
  <div class="label-and-help">
168
180
  ${this.label && String(this.label).trim()
169
181
  ? html`<div class="checkbox-label">${this.label}</div>`
@@ -133,6 +133,7 @@ export class FieldRenderer {
133
133
  .value="${value || ''}"
134
134
  placeholder="${config.placeholder || ''}"
135
135
  .helpText="${config.helpText || ''}"
136
+ flavor="${config.flavor || 'default'}"
136
137
  class="${extraClasses}"
137
138
  style="${style}"
138
139
  @input="${onChange || (() => {})}"
@@ -259,12 +260,29 @@ export class FieldRenderer {
259
260
  value: any,
260
261
  context: FieldRenderContext
261
262
  ): TemplateResult {
262
- const { errors = [], onChange, extraClasses, style } = context;
263
+ const {
264
+ errors = [],
265
+ onChange,
266
+ extraClasses,
267
+ style,
268
+ formData = {}
269
+ } = context;
270
+
271
+ // Handle dynamic labels
272
+ const label =
273
+ typeof config.label === 'function'
274
+ ? config.label(formData)
275
+ : config.label;
276
+
277
+ // Build custom style including labelPadding
278
+ const customStyle = config.labelPadding
279
+ ? `--checkbox-padding: ${config.labelPadding}; ${style || ''}`
280
+ : style || '';
263
281
 
264
282
  return html`<div class="form-field">
265
283
  <temba-checkbox
266
284
  name="${fieldName}"
267
- label="${config.label}"
285
+ label="${label}"
268
286
  .helpText="${config.helpText || ''}"
269
287
  ?required="${config.required}"
270
288
  .errors="${errors}"
@@ -272,7 +290,7 @@ export class FieldRenderer {
272
290
  size="${config.size || 1.2}"
273
291
  animateChange="${config.animateChange || 'pulse'}"
274
292
  class="${extraClasses}"
275
- style="${style}"
293
+ style="${customStyle}"
276
294
  @change="${onChange || (() => {})}"
277
295
  ></temba-checkbox>
278
296
  ${errors.length
@@ -338,6 +356,7 @@ export class FieldRenderer {
338
356
  .itemLabel="${config.itemLabel || 'Item'}"
339
357
  .minItems="${config.minItems || 0}"
340
358
  .maxItems="${config.maxItems || 0}"
359
+ ?maintainEmptyItem="${config.maintainEmptyItem !== false}"
341
360
  .onItemChange="${config.onItemChange}"
342
361
  .isEmptyItemFn="${config.isEmptyItem}"
343
362
  class="${extraClasses}"
@@ -404,4 +423,6 @@ export interface FieldRenderContext {
404
423
  style?: string;
405
424
  /** Additional data needed for specific field types */
406
425
  additionalData?: Record<string, any>;
426
+ /** Form data for dynamic field configurations */
427
+ formData?: Record<string, any>;
407
428
  }
@@ -30,6 +30,21 @@ export class TextInput extends FieldElement {
30
30
  caret-color: var(--input-caret);
31
31
  }
32
32
 
33
+ .xsmall {
34
+ --temba-textinput-padding: 6px 8px;
35
+ --temba-textinput-font-size: 13px;
36
+ }
37
+
38
+ .small {
39
+ --temba-textinput-padding: 6px 8px;
40
+ --temba-textinput-font-size: 14px;
41
+ }
42
+
43
+ .large {
44
+ --temba-textinput-padding: 10px 12px;
45
+ --temba-textinput-font-size: 16px;
46
+ }
47
+
33
48
  .clear-icon {
34
49
  --icon-color: var(--color-text-dark-secondary);
35
50
  cursor: pointer;
@@ -181,6 +196,9 @@ export class TextInput extends FieldElement {
181
196
  @property({ type: String })
182
197
  type = InputType.Text;
183
198
 
199
+ @property({ type: String })
200
+ flavor = 'default';
201
+
184
202
  counterElement: CharCount = null;
185
203
  cursorStart = -1;
186
204
  cursorEnd = -1;
@@ -482,7 +500,7 @@ export class TextInput extends FieldElement {
482
500
 
483
501
  return html`
484
502
  <div
485
- class="input-container"
503
+ class="input-container ${this.flavor !== 'default' ? this.flavor : ''}"
486
504
  style=${styleMap(containerStyle)}
487
505
  @click=${this.handleContainerClick}
488
506
  >
@@ -341,6 +341,13 @@ export class Select<T extends SelectOption> extends FieldElement {
341
341
  --temba-select-min-height: 2.28em;
342
342
  }
343
343
 
344
+ .xsmall {
345
+ --temba-select-selected-padding: 4px 6px;
346
+ --temba-select-selected-line-height: 13px;
347
+ --temba-select-selected-font-size: 13px;
348
+ --temba-select-min-height: 0em;
349
+ }
350
+
344
351
  .info-text {
345
352
  opacity: 1;
346
353
  transition: padding-top var(--transition-speed) ease-in-out,
package/src/interfaces.ts CHANGED
@@ -181,7 +181,7 @@ export interface Contact {
181
181
  last_seen_on: string;
182
182
  status: string;
183
183
 
184
- anon_display?: string;
184
+ ref?: string; // only returned for anon workspaces
185
185
  flow?: ObjectReference;
186
186
  last_msg?: Msg;
187
187
  direction?: string;
@@ -24,7 +24,8 @@ export class Dialog extends ResizeElement {
24
24
  return {
25
25
  small: '400px',
26
26
  medium: '600px',
27
- large: '655px'
27
+ large: '655px',
28
+ xlarge: '800px'
28
29
  };
29
30
  }
30
31
 
@@ -413,10 +414,9 @@ export class Dialog extends ResizeElement {
413
414
 
414
415
  public render(): TemplateResult {
415
416
  const dialogStyle = {
416
- width: this.width,
417
- minWidth: '250px',
418
- maxWidth: '600px'
417
+ width: this.width
419
418
  };
419
+
420
420
  if (!this.width) {
421
421
  dialogStyle['width'] = Dialog.widths[this.size];
422
422
  }
@@ -155,7 +155,7 @@ export class RunList extends TembaList {
155
155
  style="width:16em;white-space:nowrap;overflow: hidden; text-overflow: ellipsis;"
156
156
  >
157
157
  <temba-contact-name
158
- name=${run.contact?.name || run.contact?.anon_display || ''}
158
+ name=${run.contact?.name || run.contact?.ref || ''}
159
159
  urn=${run.contact?.urn || ''}
160
160
  icon-size="15"
161
161
  />
@@ -269,7 +269,7 @@ export class RunList extends TembaList {
269
269
  <temba-contact-name
270
270
  style="cursor:pointer"
271
271
  name=${this.selectedRun.contact?.name ||
272
- this.selectedRun.contact?.anon_display ||
272
+ this.selectedRun.contact?.ref ||
273
273
  ''}
274
274
  urn=${this.selectedRun.contact?.urn || ''}
275
275
  onclick="goto(event, this)"