@nyaruka/temba-components 0.130.1 → 0.130.3

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 (250) hide show
  1. package/CHANGELOG.md +34 -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 +787 -659
  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 +101 -44
  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/src/webchat/index.js +0 -11
  59. package/out-tsc/src/webchat/index.js.map +1 -1
  60. package/out-tsc/test/NodeHelper.js +25 -27
  61. package/out-tsc/test/NodeHelper.js.map +1 -1
  62. package/out-tsc/test/nodes/split_by_llm.test.js +12 -4
  63. package/out-tsc/test/nodes/split_by_llm.test.js.map +1 -1
  64. package/out-tsc/test/nodes/split_by_llm_categorize.test.js +101 -91
  65. package/out-tsc/test/nodes/split_by_llm_categorize.test.js.map +1 -1
  66. package/out-tsc/test/nodes/split_by_random.test.js +120 -112
  67. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
  68. package/out-tsc/test/nodes/wait_for_digits.test.js +131 -111
  69. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  70. package/out-tsc/test/nodes/wait_for_response.test.js +549 -85
  71. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
  72. package/out-tsc/test/temba-checkbox.test.js +32 -32
  73. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  74. package/out-tsc/test/temba-contact-chat.test.js +2 -1
  75. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  76. package/out-tsc/test/temba-dropdown.test.js +0 -4
  77. package/out-tsc/test/temba-dropdown.test.js.map +1 -1
  78. package/out-tsc/test/temba-flow-editor-node.test.js +9 -4
  79. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  80. package/out-tsc/test/temba-integration-markdown.test.js +13 -15
  81. package/out-tsc/test/temba-integration-markdown.test.js.map +1 -1
  82. package/out-tsc/test/temba-node-editor.test.js +5 -38
  83. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  84. package/out-tsc/test/temba-run-list.test.js +2 -2
  85. package/out-tsc/test/temba-run-list.test.js.map +1 -1
  86. package/out-tsc/test/utils.test.js +2 -1
  87. package/out-tsc/test/utils.test.js.map +1 -1
  88. package/package.json +6 -2
  89. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  90. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  91. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  92. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  93. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  94. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  95. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  96. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  97. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  98. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  99. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  100. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  101. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  102. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  103. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  104. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  105. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  106. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  107. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  108. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  109. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  110. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  111. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  112. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  113. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  114. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  115. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  116. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  117. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  118. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  119. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  120. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  121. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  122. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  123. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  124. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  125. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  126. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  127. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  128. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  129. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  130. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  131. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  132. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  133. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  134. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  135. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  136. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  137. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  138. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  139. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  140. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  141. package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
  142. package/screenshots/truth/checkbox/checkbox-whitespace-label-no-background-hover.png +0 -0
  143. package/screenshots/truth/checkbox/checkbox-with-help-text.png +0 -0
  144. package/screenshots/truth/checkbox/checked.png +0 -0
  145. package/screenshots/truth/checkbox/default.png +0 -0
  146. package/screenshots/truth/editor/wait.png +0 -0
  147. package/screenshots/truth/integration/textinput-markdown-errors.png +0 -0
  148. package/screenshots/truth/lightbox/img-zoomed.png +0 -0
  149. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  150. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  151. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  152. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  153. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  154. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  155. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  156. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  157. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  158. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  159. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  160. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  161. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  162. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  163. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  164. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  165. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  166. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  167. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  168. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  169. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  170. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  171. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  172. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  173. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  174. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  175. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  176. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  177. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  178. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  179. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  180. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  181. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  182. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  183. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  184. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  185. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  186. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  187. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  188. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  189. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  190. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  191. package/screenshots/truth/run-list/basic.png +0 -0
  192. package/screenshots/truth/templates/default.png +0 -0
  193. package/screenshots/truth/wait-for-response/rules-editor.png +0 -0
  194. package/screenshots/truth/wait-for-response/timeout-editor-unchecked.png +0 -0
  195. package/screenshots/truth/wait-for-response/timeout-editor.png +0 -0
  196. package/scripts/dev-data-sync.mjs +182 -0
  197. package/src/display/Chat.ts +6 -4
  198. package/src/events.ts +6 -6
  199. package/src/flow/CanvasNode.ts +89 -79
  200. package/src/flow/Editor.ts +1 -0
  201. package/src/flow/NodeEditor.ts +55 -3
  202. package/src/flow/actions/add_contact_urn.ts +1 -1
  203. package/src/flow/actions/set_contact_channel.ts +1 -1
  204. package/src/flow/actions/set_contact_field.ts +2 -1
  205. package/src/flow/actions/set_contact_language.ts +3 -1
  206. package/src/flow/actions/set_contact_name.ts +1 -1
  207. package/src/flow/actions/set_contact_status.ts +18 -18
  208. package/src/flow/actions/set_run_result.ts +1 -1
  209. package/src/flow/nodes/split_by_llm.ts +14 -13
  210. package/src/flow/nodes/wait_for_response.ts +717 -5
  211. package/src/flow/operators.ts +215 -0
  212. package/src/flow/types.ts +10 -2
  213. package/src/form/ArrayEditor.ts +117 -37
  214. package/src/form/Checkbox.ts +12 -0
  215. package/src/form/FieldRenderer.ts +24 -3
  216. package/src/form/TextInput.ts +19 -1
  217. package/src/form/select/Select.ts +7 -0
  218. package/src/interfaces.ts +1 -1
  219. package/src/layout/Dialog.ts +4 -4
  220. package/src/list/RunList.ts +2 -2
  221. package/src/live/ContactChat.ts +128 -67
  222. package/src/live/ContactDetails.ts +7 -0
  223. package/src/live/ContactNameFetch.ts +1 -1
  224. package/src/webchat/index.ts +0 -16
  225. package/static/api/labels.json +6 -1
  226. package/test/NodeHelper.ts +38 -40
  227. package/test/nodes/split_by_llm.test.ts +43 -32
  228. package/test/nodes/split_by_llm_categorize.test.ts +130 -120
  229. package/test/nodes/split_by_random.test.ts +136 -128
  230. package/test/nodes/wait_for_digits.test.ts +147 -127
  231. package/test/nodes/wait_for_response.test.ts +657 -104
  232. package/test/temba-checkbox.test.ts +36 -32
  233. package/test/temba-contact-chat.test.ts +2 -1
  234. package/test/temba-dropdown.test.ts +0 -12
  235. package/test/temba-flow-editor-node.test.ts +11 -4
  236. package/test/temba-integration-markdown.test.ts +16 -17
  237. package/test/temba-node-editor.test.ts +5 -43
  238. package/test/temba-run-list.test.ts +2 -2
  239. package/test/utils.test.ts +2 -1
  240. package/test-assets/contacts/history.json +4 -7
  241. package/test-assets/list/runs.json +8 -8
  242. package/web-dev-mock.mjs +86 -30
  243. package/web-dev-server.config.mjs +272 -31
  244. package/screenshots/truth/dropdown/bottom-edge-collision.png +0 -0
  245. package/screenshots/truth/dropdown/right-edge-collision.png +0 -0
  246. package/screenshots/truth/editor/send_msg.png +0 -0
  247. package/screenshots/truth/editor/set_contact_language.png +0 -0
  248. package/screenshots/truth/editor/set_contact_name.png +0 -0
  249. package/screenshots/truth/editor/set_run_result.png +0 -0
  250. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
@@ -4,7 +4,6 @@ import { RapidElement } from '../RapidElement';
4
4
  import { CustomEventType } from '../interfaces';
5
5
  import { DEFAULT_AVATAR } from '../webchat/assets';
6
6
  import { hashCode } from '../utils';
7
- import { renderMarkdown } from '../markdown';
8
7
 
9
8
  const BATCH_TIME_WINDOW = 60 * 60 * 1000;
10
9
  const SCROLL_FETCH_BUFFER = 0.05;
@@ -28,7 +27,7 @@ interface User {
28
27
  export interface ChatEvent {
29
28
  id?: string;
30
29
  type: MessageType;
31
- text: string;
30
+ text: TemplateResult;
32
31
  date: Date;
33
32
  user?: User;
34
33
  popup?: TemplateResult;
@@ -534,7 +533,10 @@ export class Chat extends RapidElement {
534
533
  // make sure our messages have ids
535
534
  messages.forEach((m) => {
536
535
  if (!m.id) {
537
- m.id = hashCode(m.text) + '_' + m.date.toISOString();
536
+ m.id =
537
+ hashCode((m.text.strings || []).join('')) +
538
+ '_' +
539
+ m.date.toISOString();
538
540
  }
539
541
  });
540
542
 
@@ -764,7 +766,7 @@ export class Chat extends RapidElement {
764
766
  event.type === MessageType.Collapse ||
765
767
  event.type === MessageType.Inline
766
768
  ) {
767
- return html`<div class="event">${renderMarkdown(event.text)}</div>`;
769
+ return html`<div class="event">${event.text}</div>`;
768
770
  }
769
771
 
770
772
  const message = event as Message;
package/src/events.ts CHANGED
@@ -10,7 +10,8 @@ export interface ContactEvent {
10
10
  uuid?: string;
11
11
  type: string;
12
12
  created_on: string;
13
- created_by?: User;
13
+ created_by?: User; // deprecated
14
+ _user?: ObjectReference;
14
15
  }
15
16
 
16
17
  export interface ChannelEvent extends ContactEvent {
@@ -75,16 +76,15 @@ export interface URNsChangedEvent extends ContactEvent {
75
76
  }
76
77
 
77
78
  export interface TicketEvent extends ContactEvent {
78
- note?: string;
79
- assignee?: User;
80
79
  ticket: {
80
+ // ticket_opened
81
81
  uuid: string;
82
82
  topic?: ObjectReference;
83
- closed_on?: string;
84
- opened_on?: string;
85
83
  };
84
+ ticket_uuid?: string; // all other event types
85
+ assignee?: User;
86
+ note?: string;
86
87
  topic?: ObjectReference;
87
- created_by?: User;
88
88
  }
89
89
 
90
90
  export interface NameChangedEvent extends ContactEvent {
@@ -78,60 +78,43 @@ export class CanvasNode extends RapidElement {
78
78
 
79
79
  .action {
80
80
  position: relative;
81
- font-size: 13px;
82
81
  }
83
82
 
84
- .action .remove-button {
85
- position: absolute;
86
- top: 5px;
87
- right: 5px;
88
- width: 16px;
89
- height: 16px;
90
- border-radius: 50%;
91
- background: var(--color-error, #dc3545);
92
- color: white;
93
- border: none;
94
- cursor: pointer;
95
- display: none;
96
- align-items: center;
97
- justify-content: center;
98
- font-size: 10px;
99
- line-height: 1;
100
- z-index: 10;
101
- }
102
83
 
103
- .action:hover .remove-button,
84
+ .action .cn-title:hover .remove-button,
104
85
  .router:hover .remove-button {
105
- display: flex;
86
+ opacity: 0.7;
106
87
  }
107
88
 
108
- .action.removing .title,
109
- .router .title.removing {
89
+ .action.removing .cn-title,
90
+ .router .cn-title.removing {
110
91
  background-color: var(--color-error, #dc3545) !important;
111
92
  }
112
93
 
113
- .action.removing .title .name,
114
- .router .title.removing .name {
94
+ .action.removing .cn-title .name,
95
+ .router .cn-title.removing .name {
115
96
  color: white;
116
97
  }
117
98
 
118
- .router .remove-button {
119
- position: absolute;
120
- top: 5px;
121
- right: 5px;
122
- width: 16px;
123
- height: 16px;
124
- border-radius: 50%;
125
- background: var(--color-error, #dc3545);
99
+ .remove-button {
100
+ background: transparent;
126
101
  color: white;
127
- border: none;
102
+ opacity: 0;
128
103
  cursor: pointer;
129
- display: none;
130
- align-items: center;
131
- justify-content: center;
132
- font-size: 10px;
104
+ font-size: 1em;
105
+ font-weight: 600;
133
106
  line-height: 1;
134
107
  z-index: 10;
108
+ transition: all 100ms ease-in-out;
109
+ align-self: center;
110
+ padding:0.25em;
111
+ border: 0px solid red;
112
+ width: 1em;
113
+ pointer-events: auto; /* Ensure remove button can receive events */
114
+ }
115
+
116
+ .remove-button:hover {
117
+ opacity: 1;
135
118
  }
136
119
 
137
120
  .action.sortable {
@@ -165,14 +148,24 @@ export class CanvasNode extends RapidElement {
165
148
  transition: all 200ms ease-in-out;
166
149
  cursor: move;
167
150
  background: rgba(0, 0, 0, 0.02);
168
- max-width:0px;
169
- position: absolute;
151
+ width: 1em;
152
+ padding: 0.25em;
153
+ border: 0px solid red;
154
+ pointer-events: auto; /* Ensure drag handle can receive events */
155
+ }
156
+ .title-spacer {
157
+ width: 2em;
158
+
170
159
  }
171
160
 
172
161
  .action:hover .drag-handle {
173
- opacity: 0.5;
174
- padding: 0.25em;
175
- max-width: 20px;
162
+ opacity: 0.7;
163
+
164
+
165
+ }
166
+
167
+ strong {
168
+ font-weight: 500;
176
169
  }
177
170
 
178
171
  .action .drag-handle:hover {
@@ -180,20 +173,27 @@ export class CanvasNode extends RapidElement {
180
173
 
181
174
  }
182
175
 
183
- .action .title,
184
- .router .title {
176
+ .action .cn-title,
177
+ .router .cn-title {
185
178
  display: flex;
186
179
  color: #fff;
187
- padding: 5px 1px;
188
180
  text-align: center;
189
181
  font-size: 1em;
190
- font-weight: normal;
182
+ font-weight: 500;
183
+ }
184
+
185
+ .cn-title .name {
186
+ padding: 0.3em 0;
191
187
 
192
188
  }
193
189
 
194
- .title .name {
190
+ .router .cn-title {
191
+
192
+ }
193
+
194
+ .cn-title .name {
195
195
  flex-grow: 1;
196
- }
196
+ }
197
197
 
198
198
  .quick-replies {
199
199
  margin-top: 0.5em;
@@ -230,7 +230,7 @@ export class CanvasNode extends RapidElement {
230
230
  margin-top: -0.8em;
231
231
  }
232
232
 
233
- .category .title {
233
+ .category .cn-title {
234
234
  font-weight: normal;
235
235
  font-size: 1em;
236
236
  max-width: 150px;
@@ -244,7 +244,7 @@ export class CanvasNode extends RapidElement {
244
244
  }
245
245
 
246
246
  .result-name {
247
- font-weight: bold;
247
+ font-weight: 500;
248
248
  display: inline-block;
249
249
  }
250
250
 
@@ -320,7 +320,7 @@ export class CanvasNode extends RapidElement {
320
320
  border-bottom-right-radius: var(--curvature);
321
321
  }
322
322
 
323
- .router .title {
323
+ .router .cn-title {
324
324
  border-top-left-radius: var(--curvature);
325
325
  border-top-right-radius: var(--curvature);
326
326
  }
@@ -329,7 +329,7 @@ export class CanvasNode extends RapidElement {
329
329
  overflow: hidden;
330
330
  }
331
331
 
332
- .action:first-child .title {
332
+ .action:first-child .cn-title {
333
333
  border-top-left-radius: var(--curvature);
334
334
  border-top-right-radius: var(--curvature);
335
335
  }
@@ -610,10 +610,11 @@ export class CanvasNode extends RapidElement {
610
610
  }
611
611
 
612
612
  private handleActionMouseDown(event: MouseEvent, action: Action): void {
613
- // Don't handle clicks on the remove button or when action is in removing state
613
+ // Don't handle clicks on the remove button, drag handle, or when action is in removing state
614
614
  const target = event.target as HTMLElement;
615
615
  if (
616
616
  target.closest('.remove-button') ||
617
+ target.closest('.drag-handle') ||
617
618
  this.actionRemovingState.has(action.uuid)
618
619
  ) {
619
620
  return;
@@ -636,10 +637,11 @@ export class CanvasNode extends RapidElement {
636
637
  return;
637
638
  }
638
639
 
639
- // Don't handle clicks on the remove button or when action is in removing state
640
+ // Don't handle clicks on the remove button, drag handle, or when action is in removing state
640
641
  const target = event.target as HTMLElement;
641
642
  if (
642
643
  target.closest('.remove-button') ||
644
+ target.closest('.drag-handle') ||
643
645
  this.actionRemovingState.has(action.uuid)
644
646
  ) {
645
647
  this.actionClickStartPos = null;
@@ -727,12 +729,13 @@ export class CanvasNode extends RapidElement {
727
729
  }
728
730
 
729
731
  private handleNodeMouseDown(event: MouseEvent): void {
730
- // Don't handle clicks on the remove button, exits, or when node is in removing state
732
+ // Don't handle clicks on the remove button, exits, drag handle, or when node is in removing state
731
733
  const target = event.target as HTMLElement;
732
734
  if (
733
735
  target.closest('.remove-button') ||
734
736
  target.closest('.exit') ||
735
737
  target.closest('.exit-wrapper') ||
738
+ target.closest('.drag-handle') ||
736
739
  this.actionRemovingState.has(this.node.uuid)
737
740
  ) {
738
741
  return;
@@ -752,12 +755,13 @@ export class CanvasNode extends RapidElement {
752
755
  return;
753
756
  }
754
757
 
755
- // Don't handle clicks on the remove button, exits, or when node is in removing state
758
+ // Don't handle clicks on the remove button, exits, drag handle, or when node is in removing state
756
759
  const target = event.target as HTMLElement;
757
760
  if (
758
761
  target.closest('.remove-button') ||
759
762
  target.closest('.exit') ||
760
763
  target.closest('.exit-wrapper') ||
764
+ target.closest('.drag-handle') ||
761
765
  this.actionRemovingState.has(this.node.uuid)
762
766
  ) {
763
767
  this.nodeClickStartPos = null;
@@ -803,22 +807,43 @@ export class CanvasNode extends RapidElement {
803
807
  this.pendingNodeClick = null;
804
808
  }
805
809
 
806
- private renderTitle(config: ActionConfig, isRemoving: boolean = false) {
807
- return html`<div class="title" style="background:${config.color}">
810
+ private renderTitle(
811
+ config: ActionConfig,
812
+ action: Action,
813
+ index: number,
814
+ isRemoving: boolean = false
815
+ ) {
816
+ return html`<div class="cn-title" style="background:${config.color}">
808
817
  ${this.node?.actions?.length > 1
809
818
  ? html`<temba-icon class="drag-handle" name="sort"></temba-icon>`
810
819
  : null}
811
820
 
812
821
  <div class="name">${isRemoving ? 'Remove?' : config.name}</div>
822
+ <div
823
+ class="remove-button"
824
+ @click=${(e: MouseEvent) =>
825
+ this.handleActionRemoveClick(e, action, index)}
826
+ title="Remove action"
827
+ >
828
+
829
+ </div>
813
830
  </div>`;
814
831
  }
815
832
 
816
833
  private renderNodeTitle(config: NodeConfig, isRemoving: boolean = false) {
817
834
  return html`<div
818
- class="title ${isRemoving ? 'removing' : ''}"
835
+ class="cn-title ${isRemoving ? 'removing' : ''}"
819
836
  style="background:${config.color}"
820
837
  >
838
+ <div class="title-spacer"></div>
821
839
  <div class="name">${isRemoving ? 'Remove?' : config.name}</div>
840
+ <div
841
+ class="remove-button"
842
+ @click=${(e: MouseEvent) => this.handleNodeRemoveClick(e)}
843
+ title="Remove node"
844
+ >
845
+
846
+ </div>
822
847
  </div>`;
823
848
  }
824
849
 
@@ -831,21 +856,13 @@ export class CanvasNode extends RapidElement {
831
856
  class="action sortable ${action.type} ${isRemoving ? 'removing' : ''}"
832
857
  id="action-${index}"
833
858
  >
834
- <button
835
- class="remove-button"
836
- @click=${(e: MouseEvent) =>
837
- this.handleActionRemoveClick(e, action, index)}
838
- title="Remove action"
839
- >
840
-
841
- </button>
842
859
  <div
843
860
  class="action-content"
844
861
  @mousedown=${(e: MouseEvent) => this.handleActionMouseDown(e, action)}
845
862
  @mouseup=${(e: MouseEvent) => this.handleActionMouseUp(e, action)}
846
863
  style="cursor: pointer;"
847
864
  >
848
- ${this.renderTitle(config, isRemoving)}
865
+ ${this.renderTitle(config, action, index, isRemoving)}
849
866
  <div class="body">
850
867
  ${config.render
851
868
  ? config.render(node, action)
@@ -858,14 +875,14 @@ export class CanvasNode extends RapidElement {
858
875
  class="action sortable ${isRemoving ? 'removing' : ''}"
859
876
  id="action-${index}"
860
877
  >
861
- <button
878
+ <div
862
879
  class="remove-button"
863
880
  @click=${(e: MouseEvent) =>
864
881
  this.handleActionRemoveClick(e, action, index)}
865
882
  title="Remove action"
866
883
  >
867
884
 
868
- </button>
885
+ </div>
869
886
  ${action.type}
870
887
  </div>`;
871
888
  }
@@ -909,7 +926,7 @@ export class CanvasNode extends RapidElement {
909
926
  @mouseup=${(e: MouseEvent) => this.handleNodeMouseUp(e)}
910
927
  style="cursor: pointer;"
911
928
  >
912
- <div class="title">${category.name}</div>
929
+ <div class="cn-title">${category.name}</div>
913
930
  ${this.renderExit(exit)}
914
931
  </div>`;
915
932
  }
@@ -948,13 +965,6 @@ export class CanvasNode extends RapidElement {
948
965
  >
949
966
  ${nodeConfig && nodeConfig.type !== 'execute_actions'
950
967
  ? html`<div class="router" style="position: relative;">
951
- <button
952
- class="remove-button"
953
- @click=${(e: MouseEvent) => this.handleNodeRemoveClick(e)}
954
- title="Remove node"
955
- >
956
-
957
- </button>
958
968
  <div
959
969
  @mousedown=${(e: MouseEvent) => this.handleNodeMouseDown(e)}
960
970
  @mouseup=${(e: MouseEvent) => this.handleNodeMouseUp(e)}
@@ -143,6 +143,7 @@ export class Editor extends RapidElement {
143
143
  #editor {
144
144
  overflow: scroll;
145
145
  flex: 1;
146
+ -webkit-font-smoothing: antialiased;
146
147
  }
147
148
 
148
149
  #grid {
@@ -85,7 +85,7 @@ export class NodeEditor extends RapidElement {
85
85
  .form-row {
86
86
  display: grid;
87
87
  gap: 1rem;
88
- align-items: end;
88
+ align-items: center;
89
89
  }
90
90
 
91
91
  .form-group {
@@ -257,6 +257,25 @@ export class NodeEditor extends RapidElement {
257
257
  display: flex;
258
258
  align-items: center;
259
259
  }
260
+
261
+ .gutter-fields {
262
+ display: flex;
263
+ flex-direction: column;
264
+ gap: 10px;
265
+ align-items: flex-start;
266
+ }
267
+
268
+ .gutter-fields .form-row {
269
+ margin: 0;
270
+ }
271
+
272
+ .gutter-fields temba-checkbox {
273
+ align-self: flex-start;
274
+ }
275
+
276
+ .gutter-fields temba-select {
277
+ min-width: 120px;
278
+ }
260
279
  `;
261
280
  }
262
281
 
@@ -1059,6 +1078,7 @@ export class NodeEditor extends RapidElement {
1059
1078
  }
1060
1079
  },
1061
1080
  showLabel: true,
1081
+ formData: this.formData,
1062
1082
  additionalData: {
1063
1083
  attachments: this.formData.attachments || []
1064
1084
  }
@@ -1420,14 +1440,24 @@ export class NodeEditor extends RapidElement {
1420
1440
  if (config.layout) {
1421
1441
  const renderedFields = new Set<string>();
1422
1442
 
1443
+ // Also collect fields from gutter to avoid rendering them in main form
1444
+ const gutterFields = new Set<string>();
1445
+ if (config.gutter) {
1446
+ const gutterFieldNames = this.collectFieldsFromItems(config.gutter);
1447
+ gutterFieldNames.forEach((field) => gutterFields.add(field));
1448
+ }
1449
+
1423
1450
  return html`
1424
1451
  ${config.layout.map((item) =>
1425
1452
  this.renderLayoutItem(item, config, renderedFields)
1426
1453
  )}
1427
1454
  ${
1428
- /* Render any fields not explicitly placed in layout */
1455
+ /* Render any fields not explicitly placed in layout or gutter */
1429
1456
  Object.entries(config.form).map(([fieldName, fieldConfig]) => {
1430
- if (!renderedFields.has(fieldName)) {
1457
+ if (
1458
+ !renderedFields.has(fieldName) &&
1459
+ !gutterFields.has(fieldName)
1460
+ ) {
1431
1461
  return this.renderNewField(
1432
1462
  fieldName,
1433
1463
  fieldConfig as FieldConfig,
@@ -1460,6 +1490,24 @@ export class NodeEditor extends RapidElement {
1460
1490
  }
1461
1491
  }
1462
1492
 
1493
+ private renderGutter(): TemplateResult {
1494
+ const config = this.getConfig();
1495
+ if (!config?.gutter || config.gutter.length === 0) {
1496
+ return html``;
1497
+ }
1498
+
1499
+ // Use the same layout rendering system for gutter fields
1500
+ const renderedFields = new Set<string>();
1501
+
1502
+ return html`
1503
+ <div class="gutter-fields">
1504
+ ${config.gutter.map((item) =>
1505
+ this.renderLayoutItem(item, config, renderedFields)
1506
+ )}
1507
+ </div>
1508
+ `;
1509
+ }
1510
+
1463
1511
  private renderActionSection(): TemplateResult {
1464
1512
  if (!this.node || this.node.actions.length === 0) {
1465
1513
  return html``;
@@ -1531,6 +1579,7 @@ export class NodeEditor extends RapidElement {
1531
1579
 
1532
1580
  const headerColor = this.getHeaderColor();
1533
1581
  const config = this.getConfig();
1582
+ const dialogSize = config?.dialogSize || 'medium'; // Default to 'large' if not specified
1534
1583
 
1535
1584
  return html`
1536
1585
  <temba-dialog
@@ -1540,6 +1589,7 @@ export class NodeEditor extends RapidElement {
1540
1589
  primaryButtonName="Save"
1541
1590
  cancelButtonName="Cancel"
1542
1591
  style="--header-bg: ${headerColor}"
1592
+ size="${dialogSize}"
1543
1593
  >
1544
1594
  <div class="node-editor-form">
1545
1595
  ${this.renderFields()}
@@ -1547,6 +1597,8 @@ export class NodeEditor extends RapidElement {
1547
1597
  ? this.renderRouterSection()
1548
1598
  : null}
1549
1599
  </div>
1600
+
1601
+ <div slot="gutter">${this.renderGutter()}</div>
1550
1602
  </temba-dialog>
1551
1603
  `;
1552
1604
  }
@@ -11,7 +11,7 @@ export const add_contact_urn: ActionConfig = {
11
11
  return html`<div
12
12
  style="word-wrap: break-word; overflow-wrap: break-word; hyphens: auto;"
13
13
  >
14
- Add ${friendlyScheme} <b>${action.path}</b>
14
+ Add ${friendlyScheme} <strong>${action.path}</strong>
15
15
  </div>`;
16
16
  }
17
17
  };
@@ -6,7 +6,7 @@ export const set_contact_channel: ActionConfig = {
6
6
  name: 'Update Channel',
7
7
  color: COLORS.update,
8
8
  render: (_node: Node, action: SetContactChannel) => {
9
- return html`<div>Set to <b>${action.channel.name}</b></div>`;
9
+ return html`<div>Set to <strong>${action.channel.name}</strong></div>`;
10
10
  },
11
11
  form: {
12
12
  channel: {
@@ -7,7 +7,8 @@ export const set_contact_field: ActionConfig = {
7
7
  color: COLORS.update,
8
8
  render: (_node: Node, action: SetContactField) => {
9
9
  return html`<div>
10
- Set <b>${action.field.name}</b> to <b>${action.value}</b>
10
+ Set <strong>${action.field.name}</strong> to
11
+ <strong>${action.value}</strong>
11
12
  </div>`;
12
13
  },
13
14
  form: {
@@ -10,7 +10,9 @@ export const set_contact_language: ActionConfig = {
10
10
  const languageNames = new Intl.DisplayNames(['en'], {
11
11
  type: 'language'
12
12
  });
13
- return html`<div>Set to <b>${languageNames.of(action.language)}</b></div>`;
13
+ return html`<div>
14
+ Set to <strong>${languageNames.of(action.language)}</strong>
15
+ </div>`;
14
16
  },
15
17
  form: {
16
18
  language: {
@@ -6,7 +6,7 @@ export const set_contact_name: ActionConfig = {
6
6
  name: 'Update Name',
7
7
  color: COLORS.update,
8
8
  render: (_node: Node, action: SetContactName) => {
9
- return html`<div>Set to <b>${action.name}</b></div>`;
9
+ return html`<div>Set to <strong>${action.name}</strong></div>`;
10
10
  },
11
11
  form: {
12
12
  name: {
@@ -1,5 +1,5 @@
1
1
  import { html } from 'lit-html';
2
- import { ActionConfig, COLORS, ValidationResult } from '../types';
2
+ import { ActionConfig, COLORS } from '../types';
3
3
  import { Node, SetContactStatus } from '../../store/flow-definition';
4
4
  import { titleCase } from '../../utils';
5
5
 
@@ -7,7 +7,23 @@ export const set_contact_status: ActionConfig = {
7
7
  name: 'Update Status',
8
8
  color: COLORS.update,
9
9
  render: (_node: Node, action: SetContactStatus) => {
10
- return html`<div>Set to <b>${titleCase(action.status)}</b></div>`;
10
+ return html`<div>Set to <strong>${titleCase(action.status)}</strong></div>`;
11
+ },
12
+ toFormData: (action: SetContactStatus) => {
13
+ return {
14
+ ...action,
15
+ status: {
16
+ name: titleCase(action.status || 'active'),
17
+ value: action.status || 'active'
18
+ }
19
+ };
20
+ },
21
+ fromFormData: (formData: any): SetContactStatus => {
22
+ return {
23
+ status: formData.status[0].value,
24
+ type: 'set_contact_status',
25
+ uuid: formData.uuid
26
+ };
11
27
  },
12
28
  form: {
13
29
  status: {
@@ -24,21 +40,5 @@ export const set_contact_status: ActionConfig = {
24
40
  ],
25
41
  helpText: 'Select the status to set for the contact'
26
42
  }
27
- },
28
- validate: (formData: SetContactStatus): ValidationResult => {
29
- const errors: { [key: string]: string } = {};
30
-
31
- if (!formData.status) {
32
- errors.status = 'Status is required';
33
- } else if (
34
- !['active', 'archived', 'stopped', 'blocked'].includes(formData.status)
35
- ) {
36
- errors.status = 'Invalid status selected';
37
- }
38
-
39
- return {
40
- valid: Object.keys(errors).length === 0,
41
- errors
42
- };
43
43
  }
44
44
  };
@@ -8,7 +8,7 @@ export const set_run_result: ActionConfig = {
8
8
  color: COLORS.save,
9
9
  render: (_node: Node, action: SetRunResult) => {
10
10
  return html`<div>
11
- Save <b>${action.value}</b> as <b>${action.name}</b>
11
+ Save <strong>${action.value}</strong> as <strong>${action.name}</strong>
12
12
  </div>`;
13
13
  },
14
14
  form: {