@nyaruka/temba-components 0.132.0 → 0.134.0

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 (181) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/demo/components/flow/example.html +1 -0
  3. package/demo/components/webchat/example.html +1 -1
  4. package/demo/static/css/tailwind.css +30019 -0
  5. package/dist/locales/es.js +5 -5
  6. package/dist/locales/es.js.map +1 -1
  7. package/dist/locales/fr.js +5 -5
  8. package/dist/locales/fr.js.map +1 -1
  9. package/dist/locales/locale-codes.js +2 -11
  10. package/dist/locales/locale-codes.js.map +1 -1
  11. package/dist/locales/pt.js +5 -5
  12. package/dist/locales/pt.js.map +1 -1
  13. package/dist/temba-components.js +555 -476
  14. package/dist/temba-components.js.map +1 -1
  15. package/out-tsc/src/display/Chat.js +248 -95
  16. package/out-tsc/src/display/Chat.js.map +1 -1
  17. package/out-tsc/src/display/FloatingTab.js +4 -4
  18. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  19. package/out-tsc/src/display/TembaUser.js +3 -3
  20. package/out-tsc/src/display/TembaUser.js.map +1 -1
  21. package/out-tsc/src/events.js.map +1 -1
  22. package/out-tsc/src/flow/CanvasNode.js +132 -58
  23. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  24. package/out-tsc/src/flow/Editor.js +183 -58
  25. package/out-tsc/src/flow/Editor.js.map +1 -1
  26. package/out-tsc/src/flow/utils.js +141 -0
  27. package/out-tsc/src/flow/utils.js.map +1 -1
  28. package/out-tsc/src/interfaces.js.map +1 -1
  29. package/out-tsc/src/layout/FloatingWindow.js +1 -2
  30. package/out-tsc/src/layout/FloatingWindow.js.map +1 -1
  31. package/out-tsc/src/list/ContentMenu.js +1 -0
  32. package/out-tsc/src/list/ContentMenu.js.map +1 -1
  33. package/out-tsc/src/list/SortableList.js +3 -2
  34. package/out-tsc/src/list/SortableList.js.map +1 -1
  35. package/out-tsc/src/live/ContactChat.js +184 -205
  36. package/out-tsc/src/live/ContactChat.js.map +1 -1
  37. package/out-tsc/src/locales/es.js +5 -5
  38. package/out-tsc/src/locales/es.js.map +1 -1
  39. package/out-tsc/src/locales/fr.js +5 -5
  40. package/out-tsc/src/locales/fr.js.map +1 -1
  41. package/out-tsc/src/locales/locale-codes.js +2 -11
  42. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  43. package/out-tsc/src/locales/pt.js +5 -5
  44. package/out-tsc/src/locales/pt.js.map +1 -1
  45. package/out-tsc/src/store/AppState.js +34 -0
  46. package/out-tsc/src/store/AppState.js.map +1 -1
  47. package/out-tsc/src/store/Store.js +5 -5
  48. package/out-tsc/src/store/Store.js.map +1 -1
  49. package/out-tsc/src/utils.js +3 -3
  50. package/out-tsc/src/utils.js.map +1 -1
  51. package/out-tsc/src/webchat/WebChat.js +22 -9
  52. package/out-tsc/src/webchat/WebChat.js.map +1 -1
  53. package/out-tsc/test/ActionHelper.js +6 -5
  54. package/out-tsc/test/ActionHelper.js.map +1 -1
  55. package/out-tsc/test/actions/send_broadcast.test.js +9 -4
  56. package/out-tsc/test/actions/send_broadcast.test.js.map +1 -1
  57. package/out-tsc/test/temba-contact-chat.test.js +1 -1
  58. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  59. package/out-tsc/test/temba-floating-window.test.js +0 -2
  60. package/out-tsc/test/temba-floating-window.test.js.map +1 -1
  61. package/out-tsc/test/temba-flow-collision.test.js +673 -0
  62. package/out-tsc/test/temba-flow-collision.test.js.map +1 -0
  63. package/out-tsc/test/temba-flow-editor-node.test.js +195 -0
  64. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  65. package/out-tsc/test/temba-utils-uuid.test.js +45 -1
  66. package/out-tsc/test/temba-utils-uuid.test.js.map +1 -1
  67. package/out-tsc/test/utils.test.js +2 -2
  68. package/out-tsc/test/utils.test.js.map +1 -1
  69. package/package.json +1 -1
  70. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  71. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  72. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  73. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  74. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  75. package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
  76. package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
  77. package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
  78. package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
  79. package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
  80. package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
  81. package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
  82. package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
  83. package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
  84. package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
  85. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  86. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  87. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  88. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  89. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  90. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  91. package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
  92. package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
  93. package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
  94. package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
  95. package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
  96. package/screenshots/truth/actions/send_broadcast/render/with-attachments.png +0 -0
  97. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  98. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  99. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  100. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  101. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  102. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  103. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  104. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  105. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  106. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  107. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  108. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  109. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  110. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  111. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  112. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  113. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  114. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  115. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  116. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  117. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  118. package/screenshots/truth/contacts/chat-failure.png +0 -0
  119. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  120. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  121. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  122. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  123. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  124. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  125. package/screenshots/truth/floating-tab/default.png +0 -0
  126. package/screenshots/truth/floating-tab/gray.png +0 -0
  127. package/screenshots/truth/floating-tab/green.png +0 -0
  128. package/screenshots/truth/floating-tab/hover.png +0 -0
  129. package/screenshots/truth/floating-tab/purple.png +0 -0
  130. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  131. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  132. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  133. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  134. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  135. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  136. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  137. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  138. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  139. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  140. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  141. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  142. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  143. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  144. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  145. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  146. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  147. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  148. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  149. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  150. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  151. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  152. package/src/display/Chat.ts +331 -135
  153. package/src/display/FloatingTab.ts +4 -4
  154. package/src/display/TembaUser.ts +3 -2
  155. package/src/events.ts +12 -12
  156. package/src/flow/CanvasNode.ts +140 -57
  157. package/src/flow/Editor.ts +240 -58
  158. package/src/flow/utils.ts +207 -1
  159. package/src/interfaces.ts +7 -0
  160. package/src/layout/FloatingWindow.ts +1 -3
  161. package/src/list/ContentMenu.ts +1 -0
  162. package/src/list/SortableList.ts +3 -2
  163. package/src/live/ContactChat.ts +195 -221
  164. package/src/locales/es.ts +13 -18
  165. package/src/locales/fr.ts +13 -18
  166. package/src/locales/locale-codes.ts +2 -11
  167. package/src/locales/pt.ts +13 -18
  168. package/src/store/AppState.ts +43 -0
  169. package/src/store/Store.ts +5 -5
  170. package/src/utils.ts +3 -3
  171. package/src/webchat/WebChat.ts +24 -10
  172. package/test/ActionHelper.ts +13 -5
  173. package/test/actions/send_broadcast.test.ts +4 -2
  174. package/test/temba-contact-chat.test.ts +1 -1
  175. package/test/temba-floating-window.test.ts +0 -2
  176. package/test/temba-flow-collision.test.ts +833 -0
  177. package/test/temba-flow-editor-node.test.ts +224 -0
  178. package/test/temba-utils-uuid.test.ts +61 -1
  179. package/test/utils.test.ts +7 -2
  180. package/test-assets/contacts/history.json +22 -9
  181. package/web-test-runner.config.mjs +3 -3
@@ -57,6 +57,7 @@ export class CanvasNode extends RapidElement {
57
57
 
58
58
  .action .cn-title:hover .remove-button,
59
59
  .router:hover .remove-button {
60
+ visibility: visible;
60
61
  opacity: 0.7;
61
62
  }
62
63
 
@@ -73,7 +74,7 @@ export class CanvasNode extends RapidElement {
73
74
  .remove-button {
74
75
  background: transparent;
75
76
  color: white;
76
- opacity: 0;
77
+ visibility: hidden;
77
78
  cursor: pointer;
78
79
  font-size: 1em;
79
80
  font-weight: 600;
@@ -81,16 +82,22 @@ export class CanvasNode extends RapidElement {
81
82
  z-index: 10;
82
83
  transition: all 100ms ease-in-out;
83
84
  align-self: center;
84
- padding:0.25em;
85
+ margin-right:0.15em;
85
86
  border: 0px solid red;
86
87
  width: 1em;
87
88
  pointer-events: auto; /* Ensure remove button can receive events */
88
89
  }
89
90
 
90
91
  .remove-button:hover {
92
+ visibility: visible;
91
93
  opacity: 1;
92
94
  }
93
95
 
96
+ .translating-hidden {
97
+ visibility: hidden !important;
98
+ pointer-events: none !important;
99
+ }
100
+
94
101
  .action.sortable {
95
102
  display: flex;
96
103
  align-items: stretch;
@@ -102,6 +109,7 @@ export class CanvasNode extends RapidElement {
102
109
  flex-direction: column;
103
110
  min-width: 0; /* Allow flex item to shrink below its content size */
104
111
  overflow: hidden;
112
+ background: #fff;
105
113
  }
106
114
 
107
115
  .action .body {
@@ -132,7 +140,7 @@ export class CanvasNode extends RapidElement {
132
140
  }
133
141
 
134
142
  .action .drag-handle {
135
- opacity: 0;
143
+ visibility: hidden;
136
144
  transition: all 200ms ease-in-out;
137
145
  cursor: move;
138
146
  background: rgba(0, 0, 0, 0.02);
@@ -147,6 +155,7 @@ export class CanvasNode extends RapidElement {
147
155
  }
148
156
 
149
157
  .action:hover .drag-handle {
158
+ visibility: visible;
150
159
  opacity: 0.7;
151
160
 
152
161
 
@@ -157,6 +166,7 @@ export class CanvasNode extends RapidElement {
157
166
  }
158
167
 
159
168
  .action .drag-handle:hover {
169
+ visibility: visible;
160
170
  opacity: 1;
161
171
 
162
172
  }
@@ -360,6 +370,18 @@ export class CanvasNode extends RapidElement {
360
370
  opacity: 1 !important;
361
371
  transform: scale(1.1);
362
372
  }
373
+
374
+ .empty-node-placeholder {
375
+ height: 60px;
376
+ background: #f3f4f6;
377
+ border: 2px dashed #d1d5db;
378
+ border-radius: var(--curvature);
379
+ display: flex;
380
+ align-items: center;
381
+ justify-content: center;
382
+ color: #9ca3af;
383
+ font-size: 0.9em;
384
+ }
363
385
  }`;
364
386
  }
365
387
  constructor() {
@@ -382,6 +404,9 @@ export class CanvasNode extends RapidElement {
382
404
  this.draggedActionHeight = 0;
383
405
  // Track external action drag (action being dragged from another node)
384
406
  this.externalDragInfo = null;
407
+ // Track if we're showing a placeholder for our own last action being dragged out
408
+ this.showLastActionPlaceholder = false;
409
+ this.lastActionPlaceholderHeight = 60;
385
410
  this.handleActionOrderChanged = this.handleActionOrderChanged.bind(this);
386
411
  this.handleActionDragStart = this.handleActionDragStart.bind(this);
387
412
  this.handleActionDragExternal = this.handleActionDragExternal.bind(this);
@@ -407,6 +432,14 @@ export class CanvasNode extends RapidElement {
407
432
  updated(changes) {
408
433
  var _b;
409
434
  super.updated(changes);
435
+ if (!!changes.get('ui') && changes.has('ui')) {
436
+ // run revalidation every 50ms until 350ms to catch animation updates
437
+ for (let delay = 25; delay <= 350; delay += 25) {
438
+ setTimeout(() => {
439
+ this.plumber.revalidate([this.node.uuid]);
440
+ }, delay);
441
+ }
442
+ }
410
443
  if (changes.has('node')) {
411
444
  // Only proceed if plumber is available (for tests that don't set it up)
412
445
  if (this.plumber) {
@@ -594,6 +627,8 @@ export class CanvasNode extends RapidElement {
594
627
  this.actionRemovalTimeouts.delete(nodeId);
595
628
  }
596
629
  // Fire the node deleted event
630
+ // The Editor will handle cleanup (Plumber connections) and call store.removeNodes()
631
+ // The store's removeNodes method handles rerouting of connections
597
632
  this.fireCustomEvent(CustomEventType.NodeDeleted, {
598
633
  uuid: this.node.uuid
599
634
  });
@@ -601,6 +636,11 @@ export class CanvasNode extends RapidElement {
601
636
  handleActionOrderChanged(event) {
602
637
  var _b;
603
638
  const [fromIdx, toIdx] = event.detail.swap;
639
+ // If we have an external drag in progress, ignore internal order changes
640
+ // as they'll be handled by the external drop handler
641
+ if (this.externalDragInfo) {
642
+ return;
643
+ }
604
644
  // swap our actions
605
645
  const newActions = [...this.node.actions];
606
646
  const movedAction = newActions.splice(fromIdx, 1)[0];
@@ -623,6 +663,12 @@ export class CanvasNode extends RapidElement {
623
663
  // Fallback to a reasonable default
624
664
  this.draggedActionHeight = 60;
625
665
  }
666
+ // If this is the last action, show placeholder
667
+ if (this.node.actions.length === 1) {
668
+ this.showLastActionPlaceholder = true;
669
+ this.lastActionPlaceholderHeight = this.draggedActionHeight;
670
+ this.requestUpdate();
671
+ }
626
672
  }
627
673
  handleActionDragExternal(event) {
628
674
  // stop propagation of the original event from SortableList
@@ -636,6 +682,8 @@ export class CanvasNode extends RapidElement {
636
682
  }
637
683
  const actionIndex = parseInt(splitId[1], 10);
638
684
  const action = this.node.actions[actionIndex];
685
+ // Check if this is the last action
686
+ const isLastAction = this.node.actions.length === 1;
639
687
  // fire event to editor to show canvas drop preview, including the captured height
640
688
  this.fireCustomEvent(CustomEventType.DragExternal, {
641
689
  action,
@@ -643,7 +691,8 @@ export class CanvasNode extends RapidElement {
643
691
  actionIndex,
644
692
  mouseX: event.detail.mouseX,
645
693
  mouseY: event.detail.mouseY,
646
- actionHeight: this.draggedActionHeight
694
+ actionHeight: this.draggedActionHeight,
695
+ isLastAction
647
696
  });
648
697
  }
649
698
  handleActionDragInternal(_event) {
@@ -654,6 +703,8 @@ export class CanvasNode extends RapidElement {
654
703
  }
655
704
  handleActionDragStop(event) {
656
705
  const isExternal = event.detail.isExternal;
706
+ // Clear last action placeholder when drag stops
707
+ this.showLastActionPlaceholder = false;
657
708
  if (isExternal) {
658
709
  // stop propagation of the original event from SortableList
659
710
  event.stopPropagation();
@@ -666,16 +717,21 @@ export class CanvasNode extends RapidElement {
666
717
  }
667
718
  const actionIndex = parseInt(split[1], 10);
668
719
  const action = this.node.actions[actionIndex];
669
- // fire event to editor to create new node
720
+ // Check if this is the last action in the node
721
+ const isLastAction = this.node.actions.length === 1;
722
+ // Always fire the DragStop event so the Editor can handle drops on other nodes
723
+ // The Editor will decide whether to create a new node or drop on existing node
670
724
  this.fireCustomEvent(CustomEventType.DragStop, {
671
725
  action,
672
726
  nodeUuid: this.node.uuid,
673
727
  actionIndex,
674
728
  isExternal: true,
729
+ isLastAction,
675
730
  mouseX: event.detail.mouseX,
676
731
  mouseY: event.detail.mouseY
677
732
  });
678
733
  }
734
+ this.requestUpdate();
679
735
  }
680
736
  handleActionMouseDown(event, action) {
681
737
  // Don't handle clicks on the remove button, drag handle, or when action is in removing state
@@ -931,7 +987,6 @@ export class CanvasNode extends RapidElement {
931
987
  const dropIndex = (_e = (_c = (_b = this.externalDragInfo) === null || _b === void 0 ? void 0 : _b.dropIndex) !== null && _c !== void 0 ? _c : (_d = this.node.actions) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0;
932
988
  // Clear external drag state
933
989
  this.externalDragInfo = null;
934
- // Remove the action from the source node
935
990
  const store = getStore();
936
991
  if (!store)
937
992
  return;
@@ -939,51 +994,64 @@ export class CanvasNode extends RapidElement {
939
994
  if (!flowDefinition)
940
995
  return;
941
996
  const sourceNode = flowDefinition.nodes.find((n) => n.uuid === sourceNodeUuid);
942
- if (sourceNode) {
943
- const updatedSourceActions = sourceNode.actions.filter((_a, idx) => idx !== actionIndex);
944
- // If source node has no actions left, remove it
945
- if (updatedSourceActions.length === 0) {
946
- this.fireCustomEvent(CustomEventType.NodeDeleted, {
947
- uuid: sourceNodeUuid
948
- });
949
- }
950
- else {
951
- // Update source node
952
- const updatedSourceNode = {
953
- ...sourceNode,
954
- actions: updatedSourceActions
955
- };
956
- (_f = getStore()) === null || _f === void 0 ? void 0 : _f.getState().updateNode(sourceNodeUuid, updatedSourceNode);
957
- }
958
- }
959
- // Add the action to this node at the calculated position
997
+ if (!sourceNode)
998
+ return;
999
+ // IMPORTANT: Add the action to this node FIRST, before removing from source
1000
+ // This ensures we don't lose the action if the source node gets deleted
960
1001
  const newActions = [...this.node.actions];
961
1002
  newActions.splice(dropIndex, 0, action);
962
1003
  const updatedNode = { ...this.node, actions: newActions };
963
- (_g = getStore()) === null || _g === void 0 ? void 0 : _g.getState().updateNode(this.node.uuid, updatedNode);
1004
+ (_f = getStore()) === null || _f === void 0 ? void 0 : _f.getState().updateNode(this.node.uuid, updatedNode);
1005
+ // Now remove the action from the source node
1006
+ const updatedSourceActions = sourceNode.actions.filter((_a, idx) => idx !== actionIndex);
1007
+ // If source node has no actions left, remove it
1008
+ if (updatedSourceActions.length === 0) {
1009
+ // Fire event to Editor so it can clean up jsPlumb connections properly
1010
+ this.fireCustomEvent(CustomEventType.NodeDeleted, {
1011
+ uuid: sourceNodeUuid
1012
+ });
1013
+ }
1014
+ else {
1015
+ // Update source node
1016
+ const updatedSourceNode = {
1017
+ ...sourceNode,
1018
+ actions: updatedSourceActions
1019
+ };
1020
+ (_g = getStore()) === null || _g === void 0 ? void 0 : _g.getState().updateNode(sourceNodeUuid, updatedSourceNode);
1021
+ }
964
1022
  // Request update
965
1023
  this.requestUpdate();
966
1024
  }
967
1025
  renderTitle(config, action, index, isRemoving = false) {
968
- var _b, _c, _d;
1026
+ var _b, _c, _d, _e;
969
1027
  const color = config.group
970
1028
  ? (_b = ACTION_GROUP_METADATA[config.group]) === null || _b === void 0 ? void 0 : _b.color
971
1029
  : '#aaaaaa';
972
1030
  return html `<div class="cn-title" style="background:${color}">
973
- ${!this.isTranslating && ((_d = (_c = this.node) === null || _c === void 0 ? void 0 : _c.actions) === null || _d === void 0 ? void 0 : _d.length) > 1
974
- ? html `<temba-icon class="drag-handle" name="sort"></temba-icon>`
975
- : html `<div class="title-spacer"></div>`}
1031
+ ${((_c = this.ui) === null || _c === void 0 ? void 0 : _c.type) === 'execute_actions'
1032
+ ? html `<temba-icon
1033
+ class="drag-handle ${this.isTranslating
1034
+ ? 'translating-hidden'
1035
+ : ''}"
1036
+ name="sort"
1037
+ ></temba-icon>`
1038
+ : ((_e = (_d = this.node) === null || _d === void 0 ? void 0 : _d.actions) === null || _e === void 0 ? void 0 : _e.length) > 1
1039
+ ? html `<temba-icon
1040
+ class="drag-handle ${this.isTranslating
1041
+ ? 'translating-hidden'
1042
+ : ''}"
1043
+ name="sort"
1044
+ ></temba-icon>`
1045
+ : html `<div class="title-spacer"></div>`}
976
1046
 
977
1047
  <div class="name">${isRemoving ? 'Remove?' : config.name}</div>
978
- ${!this.isTranslating
979
- ? html `<div
980
- class="remove-button"
981
- @click=${(e) => this.handleActionRemoveClick(e, action, index)}
982
- title="Remove action"
983
- >
984
-
985
- </div>`
986
- : html `<div class="title-spacer"></div>`}
1048
+ <div
1049
+ class="remove-button ${this.isTranslating ? 'translating-hidden' : ''}"
1050
+ @click=${(e) => this.handleActionRemoveClick(e, action, index)}
1051
+ title="Remove action"
1052
+ >
1053
+
1054
+ </div>
987
1055
  </div>`;
988
1056
  }
989
1057
  renderNodeTitle(config, node, ui, isRemoving = false) {
@@ -1005,15 +1073,13 @@ export class CanvasNode extends RapidElement {
1005
1073
  ? config.renderTitle(node, ui)
1006
1074
  : html `${config.name}`}
1007
1075
  </div>
1008
- ${!this.isTranslating
1009
- ? html `<div
1010
- class="remove-button"
1011
- @click=${(e) => this.handleNodeRemoveClick(e)}
1012
- title="Remove node"
1013
- >
1014
-
1015
- </div>`
1016
- : html `<div class="title-spacer"></div>`}
1076
+ <div
1077
+ class="remove-button ${this.isTranslating ? 'translating-hidden' : ''}"
1078
+ @click=${(e) => this.handleNodeRemoveClick(e)}
1079
+ title="Remove node"
1080
+ >
1081
+
1082
+ </div>
1017
1083
  </div>`;
1018
1084
  }
1019
1085
  renderDropPlaceholder() {
@@ -1251,18 +1317,26 @@ export class CanvasNode extends RapidElement {
1251
1317
  : this.node.actions.length > 0
1252
1318
  ? this.ui.type === 'execute_actions'
1253
1319
  ? html `<temba-sortable-list
1254
- dragHandle="drag-handle"
1255
- externalDrag
1256
- @temba-order-changed="${this.handleActionOrderChanged}"
1257
- @temba-drag-start="${this.handleActionDragStart}"
1258
- @temba-drag-external="${this.handleActionDragExternal}"
1259
- @temba-drag-internal="${this.handleActionDragInternal}"
1260
- @temba-drag-stop="${this.handleActionDragStop}"
1261
- >
1262
- ${this.renderActionsWithPlaceholder()}
1263
- </temba-sortable-list>`
1320
+ dragHandle="drag-handle"
1321
+ externalDrag
1322
+ @temba-order-changed="${this.handleActionOrderChanged}"
1323
+ @temba-drag-start="${this.handleActionDragStart}"
1324
+ @temba-drag-external="${this.handleActionDragExternal}"
1325
+ @temba-drag-internal="${this.handleActionDragInternal}"
1326
+ @temba-drag-stop="${this.handleActionDragStop}"
1327
+ >
1328
+ ${this.renderActionsWithPlaceholder()}
1329
+ </temba-sortable-list>
1330
+ ${this.showLastActionPlaceholder
1331
+ ? html `<div
1332
+ class="empty-node-placeholder"
1333
+ style="height: ${this.lastActionPlaceholderHeight}px;"
1334
+ ></div>`
1335
+ : ''}`
1264
1336
  : html `${this.node.actions.map((action, index) => this.renderAction(this.node, action, index))}`
1265
- : ''}
1337
+ : this.ui.type === 'execute_actions'
1338
+ ? html `<div class="empty-node-placeholder"></div>`
1339
+ : ''}
1266
1340
  ${this.node.router
1267
1341
  ? html `<div class="router-section">
1268
1342
  ${this.renderRouter(this.node.router, this.ui)}