@nyaruka/temba-components 0.142.1 → 0.142.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 (131) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/temba-components.js +825 -654
  3. package/dist/temba-components.js.map +1 -1
  4. package/out-tsc/src/Icons.js +1 -0
  5. package/out-tsc/src/Icons.js.map +1 -1
  6. package/out-tsc/src/flow/CanvasMenu.js +30 -35
  7. package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
  8. package/out-tsc/src/flow/CanvasNode.js +13 -8
  9. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  10. package/out-tsc/src/flow/Editor.js +18 -5
  11. package/out-tsc/src/flow/Editor.js.map +1 -1
  12. package/out-tsc/src/flow/NodeEditor.js +346 -10
  13. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  14. package/out-tsc/src/flow/NodeTypeSelector.js +2 -0
  15. package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -1
  16. package/out-tsc/src/flow/Plumber.js +3 -1
  17. package/out-tsc/src/flow/Plumber.js.map +1 -1
  18. package/out-tsc/src/flow/actions/add_contact_urn.js +2 -6
  19. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
  20. package/out-tsc/src/flow/actions/enter_flow.js +2 -2
  21. package/out-tsc/src/flow/actions/enter_flow.js.map +1 -1
  22. package/out-tsc/src/flow/actions/say_msg.js +2 -1
  23. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  24. package/out-tsc/src/flow/actions/send_broadcast.js +2 -6
  25. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
  26. package/out-tsc/src/flow/actions/send_email.js +2 -6
  27. package/out-tsc/src/flow/actions/send_email.js.map +1 -1
  28. package/out-tsc/src/flow/actions/send_msg.js +52 -35
  29. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  30. package/out-tsc/src/flow/actions/set_contact_channel.js +2 -1
  31. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  32. package/out-tsc/src/flow/actions/set_contact_field.js +4 -5
  33. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  34. package/out-tsc/src/flow/actions/set_contact_language.js +3 -3
  35. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  36. package/out-tsc/src/flow/actions/set_contact_name.js +2 -1
  37. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
  38. package/out-tsc/src/flow/actions/set_contact_status.js +2 -1
  39. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  40. package/out-tsc/src/flow/actions/set_run_result.js +3 -3
  41. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  42. package/out-tsc/src/flow/actions/start_session.js +2 -2
  43. package/out-tsc/src/flow/actions/start_session.js.map +1 -1
  44. package/out-tsc/src/flow/nodes/split_by_llm.js +4 -5
  45. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
  46. package/out-tsc/src/flow/nodes/split_by_resthook.js +3 -8
  47. package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -1
  48. package/out-tsc/src/flow/nodes/split_by_subflow.js +2 -2
  49. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
  50. package/out-tsc/src/flow/nodes/split_by_webhook.js +25 -33
  51. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
  52. package/out-tsc/src/flow/nodes/wait_for_response.js +1 -0
  53. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  54. package/out-tsc/src/flow/types.js.map +1 -1
  55. package/out-tsc/src/flow/utils.js +68 -0
  56. package/out-tsc/src/flow/utils.js.map +1 -1
  57. package/out-tsc/src/form/FieldRenderer.js +17 -2
  58. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  59. package/out-tsc/src/interfaces.js +1 -0
  60. package/out-tsc/src/interfaces.js.map +1 -1
  61. package/out-tsc/src/simulator/Simulator.js +1 -1
  62. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  63. package/out-tsc/test/temba-canvas-menu.test.js +13 -9
  64. package/out-tsc/test/temba-canvas-menu.test.js.map +1 -1
  65. package/out-tsc/test/temba-flow-reflow.test.js.map +1 -1
  66. package/out-tsc/test/temba-node-editor.test.js +9 -10
  67. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  68. package/out-tsc/test/temba-node-type-selector.test.js +3 -3
  69. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
  70. package/out-tsc/test/temba-simulator.test.js +2 -2
  71. package/out-tsc/test/temba-simulator.test.js.map +1 -1
  72. package/package.json +1 -1
  73. package/screenshots/truth/actions/enter_flow/render/basic-flow.png +0 -0
  74. package/screenshots/truth/actions/enter_flow/render/long-flow-name.png +0 -0
  75. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  76. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  77. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  78. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  79. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  80. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  81. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  82. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  83. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  84. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  85. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  86. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  87. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  88. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  89. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  90. package/screenshots/truth/canvas-menu/open.png +0 -0
  91. package/screenshots/truth/node-type-selector/action-mode.png +0 -0
  92. package/screenshots/truth/node-type-selector/split-mode.png +0 -0
  93. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  94. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  95. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  96. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  97. package/src/Icons.ts +1 -0
  98. package/src/flow/CanvasMenu.ts +38 -39
  99. package/src/flow/CanvasNode.ts +16 -8
  100. package/src/flow/Editor.ts +33 -6
  101. package/src/flow/NodeEditor.ts +373 -10
  102. package/src/flow/NodeTypeSelector.ts +2 -0
  103. package/src/flow/Plumber.ts +3 -1
  104. package/src/flow/actions/add_contact_urn.ts +5 -6
  105. package/src/flow/actions/enter_flow.ts +2 -2
  106. package/src/flow/actions/say_msg.ts +2 -1
  107. package/src/flow/actions/send_broadcast.ts +2 -6
  108. package/src/flow/actions/send_email.ts +2 -6
  109. package/src/flow/actions/send_msg.ts +56 -38
  110. package/src/flow/actions/set_contact_channel.ts +5 -1
  111. package/src/flow/actions/set_contact_field.ts +10 -5
  112. package/src/flow/actions/set_contact_language.ts +6 -3
  113. package/src/flow/actions/set_contact_name.ts +5 -1
  114. package/src/flow/actions/set_contact_status.ts +5 -1
  115. package/src/flow/actions/set_run_result.ts +6 -3
  116. package/src/flow/actions/start_session.ts +2 -2
  117. package/src/flow/nodes/split_by_llm.ts +5 -5
  118. package/src/flow/nodes/split_by_resthook.ts +3 -8
  119. package/src/flow/nodes/split_by_subflow.ts +2 -2
  120. package/src/flow/nodes/split_by_webhook.ts +26 -34
  121. package/src/flow/nodes/wait_for_response.ts +1 -0
  122. package/src/flow/types.ts +25 -2
  123. package/src/flow/utils.ts +81 -1
  124. package/src/form/FieldRenderer.ts +32 -3
  125. package/src/interfaces.ts +1 -0
  126. package/src/simulator/Simulator.ts +1 -1
  127. package/test/temba-canvas-menu.test.ts +13 -9
  128. package/test/temba-flow-reflow.test.ts +4 -2
  129. package/test/temba-node-editor.test.ts +9 -10
  130. package/test/temba-node-type-selector.test.ts +3 -3
  131. package/test/temba-simulator.test.ts +2 -2
@@ -7,7 +7,13 @@ import { CustomEventType } from '../interfaces';
7
7
  * Event detail for canvas menu selection
8
8
  */
9
9
  export interface CanvasMenuSelection {
10
- action: 'sticky' | 'action' | 'split' | 'reflow';
10
+ action:
11
+ | 'sticky'
12
+ | 'action'
13
+ | 'split'
14
+ | 'send_msg'
15
+ | 'wait_for_response'
16
+ | 'reflow';
11
17
  position: { x: number; y: number };
12
18
  }
13
19
 
@@ -50,13 +56,6 @@ export class CanvasMenu extends RapidElement {
50
56
 
51
57
  .menu-item temba-icon {
52
58
  --icon-color: var(--color-text);
53
- margin-top: 0.15em;
54
- }
55
-
56
- .menu-item-content {
57
- display: flex;
58
- flex-direction: column;
59
- gap: 0.15em;
60
59
  }
61
60
 
62
61
  .menu-item-title {
@@ -65,11 +64,6 @@ export class CanvasMenu extends RapidElement {
65
64
  color: var(--color-text-dark);
66
65
  }
67
66
 
68
- .menu-item-description {
69
- font-size: 0.85rem;
70
- color: var(--color-text);
71
- }
72
-
73
67
  .divider {
74
68
  height: 1px;
75
69
  background: rgba(0, 0, 0, 0.1);
@@ -90,6 +84,9 @@ export class CanvasMenu extends RapidElement {
90
84
  @property({ type: Boolean })
91
85
  public showStickyNote = true;
92
86
 
87
+ @property({ type: Boolean })
88
+ public showWaitForResponse = true;
89
+
93
90
  @property({ type: Boolean })
94
91
  public showReflow = false;
95
92
 
@@ -131,13 +128,15 @@ export class CanvasMenu extends RapidElement {
131
128
  y: number,
132
129
  clickPosition: { x: number; y: number },
133
130
  showStickyNote: boolean = true,
134
- showReflow: boolean = false
131
+ showReflow: boolean = false,
132
+ showWaitForResponse: boolean = true
135
133
  ) {
136
134
  this.x = x;
137
135
  this.y = y;
138
136
  this.clickPosition = clickPosition;
139
137
  this.showStickyNote = showStickyNote;
140
138
  this.showReflow = showReflow;
139
+ this.showWaitForResponse = showWaitForResponse;
141
140
  this.open = true;
142
141
 
143
142
  // Adjust position after menu renders to ensure it fits on screen
@@ -185,9 +184,7 @@ export class CanvasMenu extends RapidElement {
185
184
  }
186
185
  }
187
186
 
188
- private handleMenuItemClick(
189
- action: 'sticky' | 'action' | 'split' | 'reflow'
190
- ) {
187
+ private handleMenuItemClick(action: CanvasMenuSelection['action']) {
191
188
  this.fireCustomEvent(CustomEventType.Selection, {
192
189
  action,
193
190
  position: this.clickPosition
@@ -203,17 +200,32 @@ export class CanvasMenu extends RapidElement {
203
200
 
204
201
  return html`
205
202
  <div class="menu" style="left: ${this.x}px; top: ${this.y}px;">
203
+ <div
204
+ class="menu-item"
205
+ @click=${() => this.handleMenuItemClick('send_msg')}
206
+ >
207
+ <temba-icon name="send" size="1.25"></temba-icon>
208
+ <div class="menu-item-title">Send Message</div>
209
+ </div>
210
+
211
+ ${this.showWaitForResponse
212
+ ? html`
213
+ <div
214
+ class="menu-item"
215
+ @click=${() => this.handleMenuItemClick('wait_for_response')}
216
+ >
217
+ <temba-icon name="message" size="1.25"></temba-icon>
218
+ <div class="menu-item-title">Wait for Response</div>
219
+ </div>
220
+ `
221
+ : ''}
222
+
206
223
  <div
207
224
  class="menu-item"
208
225
  @click=${() => this.handleMenuItemClick('action')}
209
226
  >
210
227
  <temba-icon name="action" size="1.25"></temba-icon>
211
- <div class="menu-item-content">
212
- <div class="menu-item-title">Add Action</div>
213
- <div class="menu-item-description">
214
- Send messages, update contacts
215
- </div>
216
- </div>
228
+ <div class="menu-item-title">Add Action</div>
217
229
  </div>
218
230
 
219
231
  <div
@@ -221,10 +233,7 @@ export class CanvasMenu extends RapidElement {
221
233
  @click=${() => this.handleMenuItemClick('split')}
222
234
  >
223
235
  <temba-icon name="split" size="1.25"></temba-icon>
224
- <div class="menu-item-content">
225
- <div class="menu-item-title">Add Split</div>
226
- <div class="menu-item-description">Branch based on conditions</div>
227
- </div>
236
+ <div class="menu-item-title">Add Split</div>
228
237
  </div>
229
238
 
230
239
  ${this.showStickyNote
@@ -236,12 +245,7 @@ export class CanvasMenu extends RapidElement {
236
245
  @click=${() => this.handleMenuItemClick('sticky')}
237
246
  >
238
247
  <temba-icon name="note" size="1.25"></temba-icon>
239
- <div class="menu-item-content">
240
- <div class="menu-item-title">Add Sticky Note</div>
241
- <div class="menu-item-description">
242
- Add a note to the canvas
243
- </div>
244
- </div>
248
+ <div class="menu-item-title">Add Sticky Note</div>
245
249
  </div>
246
250
  `
247
251
  : ''}
@@ -254,12 +258,7 @@ export class CanvasMenu extends RapidElement {
254
258
  @click=${() => this.handleMenuItemClick('reflow')}
255
259
  >
256
260
  <temba-icon name="flow" size="1.25"></temba-icon>
257
- <div class="menu-item-content">
258
- <div class="menu-item-title">Reflow</div>
259
- <div class="menu-item-description">
260
- Auto-arrange nodes in this flow
261
- </div>
262
- </div>
261
+ <div class="menu-item-title">Reflow</div>
263
262
  </div>
264
263
  `
265
264
  : ''}
@@ -6,7 +6,7 @@ import { Action, Exit, Node, NodeUI, Router } from '../store/flow-definition';
6
6
  import { property } from 'lit/decorators.js';
7
7
  import { RapidElement } from '../RapidElement';
8
8
  import { getClasses } from '../utils';
9
- import { isRightClick } from './utils';
9
+ import { isRightClick, renderClamped } from './utils';
10
10
  import { Plumber } from './Plumber';
11
11
  import { getStore } from '../store/Store';
12
12
  import { CustomEventType } from '../interfaces';
@@ -349,6 +349,7 @@ export class CanvasNode extends RapidElement {
349
349
 
350
350
  .router .body {
351
351
  padding: 0.75em;
352
+ max-width: 180px;
352
353
  }
353
354
 
354
355
  .result-name {
@@ -973,11 +974,12 @@ export class CanvasNode extends RapidElement {
973
974
  private handleActionMouseDown(event: MouseEvent, action: Action): void {
974
975
  if (isRightClick(event)) return;
975
976
 
976
- // Don't handle clicks on the remove button, drag handle, or when action is in removing state
977
+ // Don't handle clicks on the remove button, drag handle, linked elements, or when action is in removing state
977
978
  const target = event.target as HTMLElement;
978
979
  if (
979
980
  target.closest('.remove-button') ||
980
981
  target.closest('.drag-handle') ||
982
+ target.closest('.linked-name') ||
981
983
  this.actionRemovingState.has(action.uuid)
982
984
  ) {
983
985
  return;
@@ -1000,11 +1002,12 @@ export class CanvasNode extends RapidElement {
1000
1002
  return;
1001
1003
  }
1002
1004
 
1003
- // Don't handle clicks on the remove button, drag handle, or when action is in removing state
1005
+ // Don't handle clicks on the remove button, drag handle, linked elements, or when action is in removing state
1004
1006
  const target = event.target as HTMLElement;
1005
1007
  if (
1006
1008
  target.closest('.remove-button') ||
1007
1009
  target.closest('.drag-handle') ||
1010
+ target.closest('.linked-name') ||
1008
1011
  this.actionRemovingState.has(action.uuid)
1009
1012
  ) {
1010
1013
  this.actionClickStartPos = null;
@@ -1102,13 +1105,14 @@ export class CanvasNode extends RapidElement {
1102
1105
  private handleNodeMouseDown(event: MouseEvent): void {
1103
1106
  if (isRightClick(event)) return;
1104
1107
 
1105
- // Don't handle clicks on the remove button, exits, drag handle, or when node is in removing state
1108
+ // Don't handle clicks on the remove button, exits, drag handle, linked elements, or when node is in removing state
1106
1109
  const target = event.target as HTMLElement;
1107
1110
  if (
1108
1111
  target.closest('.remove-button') ||
1109
1112
  target.closest('.exit') ||
1110
1113
  target.closest('.exit-wrapper') ||
1111
1114
  target.closest('.drag-handle') ||
1115
+ target.closest('.linked-name') ||
1112
1116
  this.actionRemovingState.has(this.node.uuid)
1113
1117
  ) {
1114
1118
  return;
@@ -1128,13 +1132,14 @@ export class CanvasNode extends RapidElement {
1128
1132
  return;
1129
1133
  }
1130
1134
 
1131
- // Don't handle clicks on the remove button, exits, drag handle, or when node is in removing state
1135
+ // Don't handle clicks on the remove button, exits, drag handle, linked elements, or when node is in removing state
1132
1136
  const target = event.target as HTMLElement;
1133
1137
  if (
1134
1138
  target.closest('.remove-button') ||
1135
1139
  target.closest('.exit') ||
1136
1140
  target.closest('.exit-wrapper') ||
1137
1141
  target.closest('.drag-handle') ||
1142
+ target.closest('.linked-name') ||
1138
1143
  this.actionRemovingState.has(this.node.uuid)
1139
1144
  ) {
1140
1145
  this.nodeClickStartPos = null;
@@ -1561,8 +1566,11 @@ export class CanvasNode extends RapidElement {
1561
1566
  @mouseup=${(e: MouseEvent) => this.handleNodeMouseUp(e)}
1562
1567
  style="cursor: pointer;"
1563
1568
  >
1564
- Save as
1565
- <div class="result-name">${router.result_name}</div>
1569
+ ${renderClamped(
1570
+ html`Save as
1571
+ <span class="result-name">${router.result_name}</span>`,
1572
+ `Save as ${router.result_name}`
1573
+ )}
1566
1574
  </div>`
1567
1575
  : null}
1568
1576
  </div>`;
@@ -1624,7 +1632,7 @@ export class CanvasNode extends RapidElement {
1624
1632
  @mouseup=${(e: MouseEvent) => this.handleNodeMouseUp(e)}
1625
1633
  style="cursor: pointer;"
1626
1634
  >
1627
- <div class="cn-title">${displayName}</div>
1635
+ <div class="cn-title" title="${displayName}">${displayName}</div>
1628
1636
  ${this.renderExit(exit)}
1629
1637
  </div>`;
1630
1638
  }
@@ -955,7 +955,7 @@ export class Editor extends RapidElement {
955
955
  top: 8px;
956
956
  right: 240px;
957
957
  padding: 6px 10px;
958
- z-index: 10000;
958
+ z-index: 4999;
959
959
  pointer-events: none;
960
960
  opacity: 0;
961
961
  transition: opacity 0.15s ease-in-out;
@@ -1188,8 +1188,10 @@ export class Editor extends RapidElement {
1188
1188
  x: snappedPosition.left,
1189
1189
  y: snappedPosition.top
1190
1190
  },
1191
- false
1192
- ); // Don't show sticky note option for connection drops
1191
+ false, // Don't show sticky note option for connection drops
1192
+ false,
1193
+ this.flowType === 'message'
1194
+ );
1193
1195
  }
1194
1196
  }
1195
1197
 
@@ -1617,7 +1619,11 @@ export class Editor extends RapidElement {
1617
1619
  const element = event.currentTarget as HTMLElement;
1618
1620
  // Only start dragging if clicking on the element itself, not on exits or other interactive elements
1619
1621
  const target = event.target as HTMLElement;
1620
- if (target.classList.contains('exit') || target.closest('.exit')) {
1622
+ if (
1623
+ target.classList.contains('exit') ||
1624
+ target.closest('.exit') ||
1625
+ target.closest('.linked-name')
1626
+ ) {
1621
1627
  return;
1622
1628
  }
1623
1629
 
@@ -2803,7 +2809,8 @@ export class Editor extends RapidElement {
2803
2809
  y: snappedTop
2804
2810
  },
2805
2811
  true,
2806
- hasNodes
2812
+ hasNodes,
2813
+ this.flowType === 'message'
2807
2814
  );
2808
2815
  }
2809
2816
  }
@@ -2826,7 +2833,14 @@ export class Editor extends RapidElement {
2826
2833
  const menuWidth = 265;
2827
2834
  const menuX = rect.left + rect.width / 2 - menuWidth / 2;
2828
2835
  const menuY = rect.bottom + 8;
2829
- canvasMenu.show(menuX, menuY, { x: nodeLeft, y: nodeTop }, false);
2836
+ canvasMenu.show(
2837
+ menuX,
2838
+ menuY,
2839
+ { x: nodeLeft, y: nodeTop },
2840
+ false,
2841
+ false,
2842
+ this.flowType === 'message'
2843
+ );
2830
2844
  }
2831
2845
  }
2832
2846
 
@@ -2852,6 +2866,19 @@ export class Editor extends RapidElement {
2852
2866
  this.connectionSourceX = null;
2853
2867
  this.connectionSourceY = null;
2854
2868
  this.dragFromNodeId = null;
2869
+ } else if (
2870
+ selection.action === 'send_msg' ||
2871
+ selection.action === 'wait_for_response'
2872
+ ) {
2873
+ // Go directly to the node editor (skip node type selector)
2874
+ this.handleNodeTypeSelection(
2875
+ new CustomEvent(CustomEventType.Selection, {
2876
+ detail: {
2877
+ nodeType: selection.action,
2878
+ position: selection.position
2879
+ } as NodeTypeSelection
2880
+ })
2881
+ );
2855
2882
  } else {
2856
2883
  // Show node type selector
2857
2884
  const selector = this.querySelector(