@nyaruka/temba-components 0.136.1 → 0.138.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 (73) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/demo/components/webchat/example.html +2 -2
  3. package/dist/temba-components.js +692 -622
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/display/Chat.js +123 -44
  6. package/out-tsc/src/display/Chat.js.map +1 -1
  7. package/out-tsc/src/display/FloatingTab.js +2 -2
  8. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  9. package/out-tsc/src/events/eventRenderers.js +442 -0
  10. package/out-tsc/src/events/eventRenderers.js.map +1 -0
  11. package/out-tsc/src/flow/CanvasNode.js +45 -24
  12. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  13. package/out-tsc/src/flow/Editor.js +308 -18
  14. package/out-tsc/src/flow/Editor.js.map +1 -1
  15. package/out-tsc/src/flow/NodeEditor.js +0 -1
  16. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  17. package/out-tsc/src/flow/Plumber.js +110 -64
  18. package/out-tsc/src/flow/Plumber.js.map +1 -1
  19. package/out-tsc/src/list/ShortcutList.js +1 -1
  20. package/out-tsc/src/list/ShortcutList.js.map +1 -1
  21. package/out-tsc/src/live/ContactChat.js +12 -321
  22. package/out-tsc/src/live/ContactChat.js.map +1 -1
  23. package/out-tsc/src/simulator/Simulator.js +439 -575
  24. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  25. package/out-tsc/src/store/AppState.js +12 -2
  26. package/out-tsc/src/store/AppState.js.map +1 -1
  27. package/out-tsc/test/temba-flow-editor-node.test.js +2 -1
  28. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  29. package/out-tsc/test/temba-flow-editor-revisions.test.js +106 -0
  30. package/out-tsc/test/temba-flow-editor-revisions.test.js.map +1 -0
  31. package/out-tsc/test/temba-flow-editor.test.js +14 -10
  32. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  33. package/out-tsc/test/temba-flow-plumber-connections.test.js +7 -1
  34. package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
  35. package/out-tsc/test/temba-flow-plumber.test.js +6 -0
  36. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  37. package/out-tsc/test/temba-simulator.test.js +51 -32
  38. package/out-tsc/test/temba-simulator.test.js.map +1 -1
  39. package/package.json +1 -1
  40. package/screenshots/truth/contacts/chat-failure.png +0 -0
  41. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  42. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  43. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  44. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  45. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  46. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  47. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  48. package/screenshots/truth/simulator/after-message-sent.png +0 -0
  49. package/screenshots/truth/simulator/after-reset.png +0 -0
  50. package/screenshots/truth/simulator/attachment-menu.png +0 -0
  51. package/screenshots/truth/simulator/context-expanded.png +0 -0
  52. package/screenshots/truth/simulator/context-explorer-open.png +0 -0
  53. package/screenshots/truth/simulator/event-info.png +0 -0
  54. package/screenshots/truth/simulator/image-attachment.png +0 -0
  55. package/screenshots/truth/simulator/open-initial.png +0 -0
  56. package/screenshots/truth/simulator/quick-replies.png +0 -0
  57. package/src/display/Chat.ts +123 -44
  58. package/src/display/FloatingTab.ts +2 -2
  59. package/src/events/eventRenderers.ts +527 -0
  60. package/src/flow/CanvasNode.ts +54 -29
  61. package/src/flow/Editor.ts +360 -19
  62. package/src/flow/NodeEditor.ts +0 -1
  63. package/src/flow/Plumber.ts +123 -69
  64. package/src/list/ShortcutList.ts +1 -1
  65. package/src/live/ContactChat.ts +17 -376
  66. package/src/simulator/Simulator.ts +498 -617
  67. package/src/store/AppState.ts +13 -2
  68. package/test/temba-flow-editor-node.test.ts +2 -1
  69. package/test/temba-flow-editor-revisions.test.ts +134 -0
  70. package/test/temba-flow-editor.test.ts +16 -10
  71. package/test/temba-flow-plumber-connections.test.ts +7 -1
  72. package/test/temba-flow-plumber.test.ts +6 -0
  73. package/test/temba-simulator.test.ts +64 -34
@@ -219,7 +219,13 @@ export class Chat extends RapidElement {
219
219
  display: flex;
220
220
  flex-direction: row;
221
221
  align-items: flex-start;
222
- margin-bottom: 0.25em;
222
+ margin-bottom: 8px;
223
+ }
224
+
225
+ .row.is-event {
226
+ margin-bottom: 2px;
227
+ margin-left: 0px !important;
228
+ margin-right: 4px !important;
223
229
  }
224
230
 
225
231
  .input-panel {
@@ -263,10 +269,9 @@ export class Chat extends RapidElement {
263
269
  }
264
270
 
265
271
  .bubble {
266
- padding: 0.75em;
267
- padding-bottom: 0.25em;
268
- background: var(--color-chat-in, #f1f1f1);
269
- border-radius: var(--curvature);
272
+ padding: 10px 14px;
273
+ background: var(--color-chat-in, #e5e5ea);
274
+ border-radius: 18px;
270
275
  border: var(--chat-border-in, none);
271
276
  }
272
277
 
@@ -278,7 +283,7 @@ export class Chat extends RapidElement {
278
283
  }
279
284
 
280
285
  .outgoing .latest .bubble {
281
- border-bottom-left-radius: 0;
286
+ border-bottom-left-radius: 4px;
282
287
  }
283
288
 
284
289
  .incoming .bubble-wrap {
@@ -286,13 +291,13 @@ export class Chat extends RapidElement {
286
291
  }
287
292
 
288
293
  .incoming .bubble {
289
- background: var(--color-chat-out, #3c92dd);
294
+ background: var(--color-chat-out, #007aff);
290
295
  border: var(--chat-border-out, none);
291
296
  color: white;
292
297
  }
293
298
 
294
299
  .incoming .latest .bubble {
295
- border-bottom-right-radius: 0;
300
+ border-bottom-right-radius: 4px;
296
301
  }
297
302
 
298
303
  .incoming .bubble .name {
@@ -308,16 +313,26 @@ export class Chat extends RapidElement {
308
313
  color: rgba(0, 0, 0, 0.5);
309
314
  }
310
315
 
316
+ .warning .bubble {
317
+ background: #fef3c7;
318
+ color: rgba(125, 87, 18, 0.8);
319
+ border: none;
320
+ }
321
+
322
+ .warning .bubble .name {
323
+ color: rgba(125, 87, 18, 0.6);
324
+ }
325
+
311
326
  .failed .bubble,
312
327
  .error .bubble {
313
- border: 1px solid var(--color-error);
314
- background: #ffe6e6;
315
- color: #ad4747ff;
328
+ border: none;
329
+ background: #fee3e3;
330
+ color: #c01829;
316
331
  }
317
332
 
318
333
  .error .bubble .name,
319
334
  .failed .bubble .name {
320
- color: #ad47479a;
335
+ color: rgba(192, 24, 41, 0.6);
321
336
  }
322
337
 
323
338
  .deleted .bubble {
@@ -332,15 +347,48 @@ export class Chat extends RapidElement {
332
347
 
333
348
  .message-text {
334
349
  white-space: pre-wrap;
335
- margin-bottom: 0.5em;
336
- line-height: 1.2em;
350
+ margin-bottom: 0;
351
+ line-height: 1.2;
337
352
  word-break: break-word;
353
+ font-size: 13px;
338
354
  }
339
355
 
340
356
  .message-deleted {
341
357
  font-style: italic;
342
- margin-bottom: 0.5em;
343
- line-height: 1.2em;
358
+ margin-bottom: 0;
359
+ line-height: 1.2;
360
+ font-size: 13px;
361
+ }
362
+
363
+ .quick-replies {
364
+ display: flex;
365
+ flex-wrap: wrap;
366
+ justify-content: center;
367
+ gap: 6px;
368
+ padding: 8px 0;
369
+ margin: 0 auto;
370
+ max-width: 90%;
371
+ }
372
+
373
+ .quick-reply-btn {
374
+ padding: 4px 8px;
375
+ border-radius: 18px;
376
+ border: 1px solid var(--color-chat-out, #007aff);
377
+ background: white;
378
+ color: var(--color-chat-out, #007aff);
379
+ font-size: 11px;
380
+ cursor: pointer;
381
+ transition: all 0.2s ease;
382
+ }
383
+
384
+ .quick-reply-btn:hover:not(:disabled) {
385
+ background: var(--color-chat-out, #007aff);
386
+ color: white;
387
+ }
388
+
389
+ .quick-reply-btn:disabled {
390
+ opacity: 0.5;
391
+ cursor: not-allowed;
344
392
  }
345
393
 
346
394
  .chat {
@@ -381,12 +429,21 @@ export class Chat extends RapidElement {
381
429
  overflow-x: hidden;
382
430
  -webkit-overflow-scrolling: touch;
383
431
  overflow-scrolling: touch;
384
- padding: 1em 1em 1em 1em;
385
- padding-bottom: 2.5em;
432
+ padding: var(--chat-top-padding, 1em) 1em
433
+ var(--chat-bottom-padding, 2.5em) 1em;
386
434
  display: flex;
387
435
  flex-direction: column-reverse;
388
436
  }
389
437
 
438
+ :host(.phone-screen) .scroll {
439
+ scrollbar-width: none;
440
+ -ms-overflow-style: none;
441
+ }
442
+
443
+ :host(.phone-screen) .scroll::-webkit-scrollbar {
444
+ display: none;
445
+ }
446
+
390
447
  .messages:before {
391
448
  content: '';
392
449
  background: radial-gradient(
@@ -520,6 +577,9 @@ export class Chat extends RapidElement {
520
577
  display: flex;
521
578
  flex-direction: column;
522
579
  align-items: center;
580
+ text-align: center;
581
+ font-size: 11px;
582
+ color: #8e8e93;
523
583
  }
524
584
 
525
585
  .event p {
@@ -650,6 +710,9 @@ export class Chat extends RapidElement {
650
710
  @property({ type: Boolean })
651
711
  agent = false;
652
712
 
713
+ @property({ type: Boolean })
714
+ avatars = false;
715
+
653
716
  @property({ type: Boolean, attribute: false })
654
717
  endOfHistory = false;
655
718
 
@@ -665,6 +728,9 @@ export class Chat extends RapidElement {
665
728
  @property({ type: Boolean })
666
729
  hasFooter = false;
667
730
 
731
+ @property({ type: Boolean })
732
+ showTimestamps = true;
733
+
668
734
  private msgMap = new Map<string, ContactEvent>();
669
735
  private metadataCache = new Map<string, ContactEvent>();
670
736
 
@@ -890,7 +956,7 @@ export class Chat extends RapidElement {
890
956
  }
891
957
  }
892
958
 
893
- private scrollToBottom() {
959
+ public scrollToBottom() {
894
960
  const scroll = this.shadowRoot.querySelector('.scroll');
895
961
  if (scroll) {
896
962
  scroll.scrollTop = 0;
@@ -934,10 +1000,11 @@ export class Chat extends RapidElement {
934
1000
  const name = currentMsg._user?.name;
935
1001
 
936
1002
  const showAvatar =
937
- ((currentMsg.type === 'msg_received' ||
1003
+ this.avatars &&
1004
+ (((currentMsg.type === 'msg_received' ||
938
1005
  currentMsg.type === 'msg_created') &&
939
1006
  this.agent) ||
940
- !incoming;
1007
+ !incoming);
941
1008
 
942
1009
  const isSystem = !currentMsg._user?.uuid;
943
1010
 
@@ -1000,8 +1067,10 @@ export class Chat extends RapidElement {
1000
1067
  (statusClass === 'failed' || statusClass === 'errored'));
1001
1068
  const unsendableClass = hasError ? 'error' : '';
1002
1069
  const deletedClass = msgEvent._deleted ? 'deleted' : '';
1070
+ const latestClass = index === msgIds.length - 1 ? 'latest' : '';
1071
+ const eventClass = msg._rendered ? 'is-event' : '';
1003
1072
  return html`<div
1004
- class="row message ${statusClass} ${unsendableClass} ${deletedClass}"
1073
+ class="row message ${statusClass} ${unsendableClass} ${deletedClass} ${latestClass} ${eventClass}"
1005
1074
  >
1006
1075
  ${this.renderMessage(msg, index == 0 ? name : null)}
1007
1076
  </div>`;
@@ -1035,6 +1104,12 @@ export class Chat extends RapidElement {
1035
1104
  }
1036
1105
 
1037
1106
  const message = event as MsgEvent;
1107
+
1108
+ // safety check: if msg doesn't exist, return nothing
1109
+ if (!message.msg) {
1110
+ return html``;
1111
+ }
1112
+
1038
1113
  const unsendableReason = message.msg?.unsendable_reason;
1039
1114
  const statusReason = message._status?.reason;
1040
1115
  const errorMessage = unsendableReason
@@ -1069,28 +1144,32 @@ export class Chat extends RapidElement {
1069
1144
 
1070
1145
  return html`
1071
1146
  <div class="bubble-wrap">
1072
- <div class="popup" style="white-space: nowrap;">
1073
- ${errorMessage
1074
- ? html`<div style="color: var(--color-error); margin-right: 1em;">
1075
- ${errorMessage}
1076
- </div>`
1077
- : null}
1078
- <temba-date
1079
- value="${message.created_on.toISOString()}"
1080
- display="relative"
1081
- ></temba-date>
1082
- ${logsURL
1083
- ? html`<a
1084
- style="margin-left: 1em; color: var(--color-primary-dark);"
1085
- href="${logsURL}"
1086
- target="_blank"
1087
- rel="noopener noreferrer"
1088
- ><temba-icon name="log"></temba-icon
1089
- ></a>`
1090
- : null}
1091
-
1092
- <div class="arrow">▼</div>
1093
- </div>
1147
+ ${this.showTimestamps
1148
+ ? html`<div class="popup" style="white-space: nowrap;">
1149
+ ${errorMessage
1150
+ ? html`<div
1151
+ style="color: var(--color-error); margin-right: 1em;"
1152
+ >
1153
+ ${errorMessage}
1154
+ </div>`
1155
+ : null}
1156
+ <temba-date
1157
+ value="${message.created_on.toISOString()}"
1158
+ display="relative"
1159
+ ></temba-date>
1160
+ ${logsURL
1161
+ ? html`<a
1162
+ style="margin-left: 1em; color: var(--color-primary-dark);"
1163
+ href="${logsURL}"
1164
+ target="_blank"
1165
+ rel="noopener noreferrer"
1166
+ ><temba-icon name="log"></temba-icon
1167
+ ></a>`
1168
+ : null}
1169
+
1170
+ <div class="arrow">▼</div>
1171
+ </div>`
1172
+ : null}
1094
1173
  ${isDeleted
1095
1174
  ? html`<div class="bubble">
1096
1175
  ${name ? html`<div class="name">${name}</div>` : null}
@@ -102,10 +102,10 @@ export class FloatingTab extends RapidElement {
102
102
  // auto-calculate position based on index
103
103
  const index = FloatingTab.allTabs.indexOf(this);
104
104
  if (index === -1) {
105
- this.top = 100; // default fallback
105
+ this.top = 150; // default fallback
106
106
  } else {
107
107
  // start at 100px and stack with 10px gap between tabs
108
- this.top = 100 + index * (FloatingTab.TAB_HEIGHT + 0);
108
+ this.top = 150 + index * (FloatingTab.TAB_HEIGHT + 0);
109
109
  }
110
110
  }
111
111