@nyaruka/temba-components 0.142.2 → 0.142.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 (40) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/temba-components.js +266 -192
  3. package/dist/temba-components.js.map +1 -1
  4. package/out-tsc/src/flow/CanvasMenu.js +8 -3
  5. package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
  6. package/out-tsc/src/flow/CanvasNode.js +158 -9
  7. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  8. package/out-tsc/src/flow/Editor.js +473 -17
  9. package/out-tsc/src/flow/Editor.js.map +1 -1
  10. package/out-tsc/src/flow/NodeEditor.js +8 -8
  11. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  12. package/out-tsc/src/flow/Plumber.js +89 -27
  13. package/out-tsc/src/flow/Plumber.js.map +1 -1
  14. package/out-tsc/src/flow/StickyNote.js +63 -3
  15. package/out-tsc/src/flow/StickyNote.js.map +1 -1
  16. package/out-tsc/src/flow/actions/send_msg.js +11 -8
  17. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  18. package/out-tsc/src/flow/nodes/split_by_subflow.js +3 -1
  19. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
  20. package/out-tsc/src/flow/utils.js +1 -3
  21. package/out-tsc/src/flow/utils.js.map +1 -1
  22. package/out-tsc/src/list/SortableList.js +104 -43
  23. package/out-tsc/src/list/SortableList.js.map +1 -1
  24. package/out-tsc/src/simulator/Simulator.js +5 -1
  25. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  26. package/package.json +1 -1
  27. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  28. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  29. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  30. package/src/flow/CanvasMenu.ts +12 -4
  31. package/src/flow/CanvasNode.ts +185 -9
  32. package/src/flow/Editor.ts +552 -19
  33. package/src/flow/NodeEditor.ts +12 -12
  34. package/src/flow/Plumber.ts +101 -36
  35. package/src/flow/StickyNote.ts +76 -4
  36. package/src/flow/actions/send_msg.ts +11 -8
  37. package/src/flow/nodes/split_by_subflow.ts +3 -1
  38. package/src/flow/utils.ts +1 -3
  39. package/src/list/SortableList.ts +117 -47
  40. package/src/simulator/Simulator.ts +5 -1
@@ -251,24 +251,20 @@ export class CanvasNode extends RapidElement {
251
251
  }
252
252
  .title-spacer {
253
253
  width: 1.8em;
254
-
255
254
  }
256
255
 
257
256
  .action:hover .drag-handle {
258
257
  visibility: visible;
259
258
  opacity: 0.7;
260
-
261
-
262
- }
263
-
264
- strong {
265
- font-weight: 500;
266
259
  }
267
260
 
268
261
  .action .drag-handle:hover {
269
262
  visibility: visible;
270
263
  opacity: 1;
271
-
264
+ }
265
+
266
+ strong {
267
+ font-weight: 500;
272
268
  }
273
269
 
274
270
  .action .cn-title,
@@ -295,6 +291,8 @@ export class CanvasNode extends RapidElement {
295
291
 
296
292
  .quick-replies {
297
293
  margin-top: 0.5em;
294
+ display: flex;
295
+ flex-wrap: wrap;
298
296
  }
299
297
 
300
298
  .quick-reply {
@@ -302,9 +300,14 @@ export class CanvasNode extends RapidElement {
302
300
  border: 1px solid #e0e0e0;
303
301
  border-radius: calc(var(--curvature) * 1.5);
304
302
  padding: 0.2em 1em;
305
- display: inline-block;
306
303
  font-size: 0.8em;
307
304
  margin: 0.2em;
305
+ flex: 0 1 auto;
306
+ min-width: 0;
307
+ max-width: 100%;
308
+ overflow: hidden;
309
+ text-overflow: ellipsis;
310
+ white-space: nowrap;
308
311
  }
309
312
 
310
313
  .router-section {
@@ -489,6 +492,22 @@ export class CanvasNode extends RapidElement {
489
492
  color: #9ca3af;
490
493
  font-size: 0.9em;
491
494
  }
495
+
496
+ /* On touch devices, always show interactive controls.
497
+ The .touch-device class is added to the editor on first touch. */
498
+ .touch-device .remove-button {
499
+ visibility: visible !important;
500
+ opacity: 0.7;
501
+ }
502
+
503
+ .touch-device .action .drag-handle {
504
+ visibility: visible !important;
505
+ opacity: 0.7;
506
+ }
507
+
508
+ .touch-device .add-action-button {
509
+ opacity: 0.8 !important;
510
+ }
492
511
  }`;
493
512
  }
494
513
 
@@ -1049,6 +1068,75 @@ export class CanvasNode extends RapidElement {
1049
1068
  this.pendingActionClick = null;
1050
1069
  }
1051
1070
 
1071
+ /* c8 ignore start -- touch-only handlers untestable in headless Chromium */
1072
+ private handleActionTouchStart(event: TouchEvent, action: Action): void {
1073
+ const target = event.target as HTMLElement;
1074
+ if (
1075
+ target.closest('.remove-button') ||
1076
+ target.closest('.drag-handle') ||
1077
+ target.closest('.linked-name') ||
1078
+ this.actionRemovingState.has(action.uuid)
1079
+ ) {
1080
+ return;
1081
+ }
1082
+
1083
+ const touch = event.touches[0];
1084
+ if (!touch) return;
1085
+ this.actionClickStartPos = { x: touch.clientX, y: touch.clientY };
1086
+ this.pendingActionClick = { action, event: event as any };
1087
+ }
1088
+
1089
+ private handleActionTouchEnd(event: TouchEvent, action: Action): void {
1090
+ if (
1091
+ !this.pendingActionClick ||
1092
+ this.pendingActionClick.action.uuid !== action.uuid
1093
+ ) {
1094
+ this.actionClickStartPos = null;
1095
+ this.pendingActionClick = null;
1096
+ return;
1097
+ }
1098
+
1099
+ const target = event.target as HTMLElement;
1100
+ if (
1101
+ target.closest('.remove-button') ||
1102
+ target.closest('.drag-handle') ||
1103
+ target.closest('.linked-name') ||
1104
+ this.actionRemovingState.has(action.uuid)
1105
+ ) {
1106
+ this.actionClickStartPos = null;
1107
+ this.pendingActionClick = null;
1108
+ return;
1109
+ }
1110
+
1111
+ const touch = event.changedTouches[0];
1112
+ if (this.actionClickStartPos && touch) {
1113
+ const deltaX = touch.clientX - this.actionClickStartPos.x;
1114
+ const deltaY = touch.clientY - this.actionClickStartPos.y;
1115
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
1116
+
1117
+ const editor = this.closest('temba-flow-editor') as any;
1118
+ const editorWasDragging = editor?.dragging;
1119
+
1120
+ if (distance <= DRAG_THRESHOLD && (!editor || !editorWasDragging)) {
1121
+ const actionEl = event.currentTarget as Element;
1122
+ const origin = actionEl
1123
+ ? this.getTopCenter(actionEl)
1124
+ : { x: touch.clientX, y: touch.clientY };
1125
+
1126
+ this.fireCustomEvent(CustomEventType.ActionEditRequested, {
1127
+ action,
1128
+ nodeUuid: this.node.uuid,
1129
+ originX: origin.x,
1130
+ originY: origin.y
1131
+ });
1132
+ }
1133
+ }
1134
+
1135
+ this.actionClickStartPos = null;
1136
+ this.pendingActionClick = null;
1137
+ }
1138
+ /* c8 ignore stop */
1139
+
1052
1140
  private handleActionClick(event: MouseEvent, action: Action): void {
1053
1141
  // This method is kept for backward compatibility but should not be used
1054
1142
  // The new mousedown/mouseup approach handles click vs drag properly
@@ -1192,6 +1280,84 @@ export class CanvasNode extends RapidElement {
1192
1280
  this.pendingNodeClick = null;
1193
1281
  }
1194
1282
 
1283
+ /* c8 ignore start -- touch-only handlers */
1284
+ private handleNodeTouchStart(event: TouchEvent): void {
1285
+ const target = event.target as HTMLElement;
1286
+ if (
1287
+ target.closest('.remove-button') ||
1288
+ target.closest('.exit') ||
1289
+ target.closest('.exit-wrapper') ||
1290
+ target.closest('.drag-handle') ||
1291
+ target.closest('.linked-name') ||
1292
+ this.actionRemovingState.has(this.node.uuid)
1293
+ ) {
1294
+ return;
1295
+ }
1296
+
1297
+ const touch = event.touches[0];
1298
+ if (!touch) return;
1299
+ this.nodeClickStartPos = { x: touch.clientX, y: touch.clientY };
1300
+ this.pendingNodeClick = { event: event as any };
1301
+ }
1302
+
1303
+ private handleNodeTouchEnd(event: TouchEvent): void {
1304
+ if (!this.pendingNodeClick) {
1305
+ this.nodeClickStartPos = null;
1306
+ this.pendingNodeClick = null;
1307
+ return;
1308
+ }
1309
+
1310
+ const target = event.target as HTMLElement;
1311
+ if (
1312
+ target.closest('.remove-button') ||
1313
+ target.closest('.exit') ||
1314
+ target.closest('.exit-wrapper') ||
1315
+ target.closest('.drag-handle') ||
1316
+ target.closest('.linked-name') ||
1317
+ this.actionRemovingState.has(this.node.uuid)
1318
+ ) {
1319
+ this.nodeClickStartPos = null;
1320
+ this.pendingNodeClick = null;
1321
+ return;
1322
+ }
1323
+
1324
+ const touch = event.changedTouches[0];
1325
+ if (this.nodeClickStartPos && touch) {
1326
+ const deltaX = touch.clientX - this.nodeClickStartPos.x;
1327
+ const deltaY = touch.clientY - this.nodeClickStartPos.y;
1328
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
1329
+
1330
+ const editor = this.closest('temba-flow-editor') as any;
1331
+ const editorWasDragging = editor?.dragging;
1332
+
1333
+ if (distance <= 5 && (!editor || !editorWasDragging)) {
1334
+ if (this.node.router) {
1335
+ const origin = this.getTopCenter(this);
1336
+
1337
+ if (this.node.actions && this.node.actions.length === 1) {
1338
+ this.fireCustomEvent(CustomEventType.ActionEditRequested, {
1339
+ action: this.node.actions[0],
1340
+ nodeUuid: this.node.uuid,
1341
+ originX: origin.x,
1342
+ originY: origin.y
1343
+ });
1344
+ } else {
1345
+ this.fireCustomEvent(CustomEventType.NodeEditRequested, {
1346
+ node: this.node,
1347
+ nodeUI: this.ui,
1348
+ originX: origin.x,
1349
+ originY: origin.y
1350
+ });
1351
+ }
1352
+ }
1353
+ }
1354
+ }
1355
+
1356
+ this.nodeClickStartPos = null;
1357
+ this.pendingNodeClick = null;
1358
+ }
1359
+ /* c8 ignore stop */
1360
+
1195
1361
  private handleAddActionClick(event: MouseEvent): void {
1196
1362
  event.preventDefault();
1197
1363
  event.stopPropagation();
@@ -1503,6 +1669,10 @@ export class CanvasNode extends RapidElement {
1503
1669
  !isDisabled && this.handleActionMouseDown(e, action)}
1504
1670
  @mouseup=${(e: MouseEvent) =>
1505
1671
  !isDisabled && this.handleActionMouseUp(e, action)}
1672
+ @touchstart=${(e: TouchEvent) =>
1673
+ !isDisabled && this.handleActionTouchStart(e, action)}
1674
+ @touchend=${(e: TouchEvent) =>
1675
+ !isDisabled && this.handleActionTouchEnd(e, action)}
1506
1676
  style="cursor: ${isDisabled ? 'not-allowed' : 'pointer'}"
1507
1677
  >
1508
1678
  ${this.renderTitle(config, action, index, isRemoving)}
@@ -1564,6 +1734,8 @@ export class CanvasNode extends RapidElement {
1564
1734
  class="body"
1565
1735
  @mousedown=${(e: MouseEvent) => this.handleNodeMouseDown(e)}
1566
1736
  @mouseup=${(e: MouseEvent) => this.handleNodeMouseUp(e)}
1737
+ @touchstart=${(e: TouchEvent) => this.handleNodeTouchStart(e)}
1738
+ @touchend=${(e: TouchEvent) => this.handleNodeTouchEnd(e)}
1567
1739
  style="cursor: pointer;"
1568
1740
  >
1569
1741
  ${renderClamped(
@@ -1630,6 +1802,8 @@ export class CanvasNode extends RapidElement {
1630
1802
  })}
1631
1803
  @mousedown=${(e: MouseEvent) => this.handleNodeMouseDown(e)}
1632
1804
  @mouseup=${(e: MouseEvent) => this.handleNodeMouseUp(e)}
1805
+ @touchstart=${(e: TouchEvent) => this.handleNodeTouchStart(e)}
1806
+ @touchend=${(e: TouchEvent) => this.handleNodeTouchEnd(e)}
1633
1807
  style="cursor: pointer;"
1634
1808
  >
1635
1809
  <div class="cn-title" title="${displayName}">${displayName}</div>
@@ -1705,6 +1879,8 @@ export class CanvasNode extends RapidElement {
1705
1879
  <div
1706
1880
  @mousedown=${(e: MouseEvent) => this.handleNodeMouseDown(e)}
1707
1881
  @mouseup=${(e: MouseEvent) => this.handleNodeMouseUp(e)}
1882
+ @touchstart=${(e: TouchEvent) => this.handleNodeTouchStart(e)}
1883
+ @touchend=${(e: TouchEvent) => this.handleNodeTouchEnd(e)}
1708
1884
  style="cursor: pointer;"
1709
1885
  >
1710
1886
  ${this.renderNodeTitle(