@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
@@ -44,6 +44,7 @@ export class SortableList extends RapidElement {
44
44
 
45
45
  slot > * {
46
46
  user-select: none;
47
+ touch-action: none;
47
48
  }
48
49
 
49
50
  temba-icon {
@@ -103,6 +104,9 @@ export class SortableList extends RapidElement {
103
104
  this.handleMouseMove = this.handleMouseMove.bind(this);
104
105
  this.handleMouseUp = this.handleMouseUp.bind(this);
105
106
  this.handleMouseDown = this.handleMouseDown.bind(this);
107
+ this.handleTouchMove = this.handleTouchMove.bind(this);
108
+ this.handleTouchEnd = this.handleTouchEnd.bind(this);
109
+ this.handleTouchStart = this.handleTouchStart.bind(this);
106
110
  }
107
111
 
108
112
  private getSortableElements(): Element[] {
@@ -369,48 +373,81 @@ export class SortableList extends RapidElement {
369
373
  this.downEle.insertAdjacentElement('afterend', this.dropPlaceholder);
370
374
  }
371
375
 
372
- private handleMouseDown(event: MouseEvent) {
373
- let ele = event.target as HTMLDivElement;
374
-
376
+ /**
377
+ * Shared drag-start logic for both mouse and touch.
378
+ * Returns true if a drag target was found and state was initialised.
379
+ */
380
+ private beginDrag(
381
+ target: HTMLElement,
382
+ clientX: number,
383
+ clientY: number
384
+ ): boolean {
375
385
  // if we have a drag handle, only allow dragging from that element
376
386
  if (this.dragHandle) {
377
- if (!ele.classList.contains(this.dragHandle)) {
378
- return;
387
+ if (!target.classList.contains(this.dragHandle)) {
388
+ return false;
379
389
  }
380
390
  }
381
391
 
382
- ele = ele.closest('.sortable');
383
- if (ele) {
392
+ const ele = target.closest('.sortable') as HTMLDivElement;
393
+ if (!ele) return false;
394
+
395
+ this.downEle = ele;
396
+ this.draggingId = ele.id;
397
+ this.draggingIdx = this.getRowIndex(ele.id);
398
+ this.draggingEle = ele;
399
+
400
+ const rect = ele.getBoundingClientRect();
401
+ this.originalElementRect = rect;
402
+ this.originalLayoutSize = {
403
+ width: ele.offsetWidth,
404
+ height: ele.offsetHeight
405
+ };
406
+ this.xOffset = clientX - rect.left;
407
+ this.yOffset = clientY - rect.top;
408
+ this.yDown = clientY;
409
+ this.xDown = clientX;
410
+
411
+ return true;
412
+ }
413
+
414
+ private handleMouseDown(event: MouseEvent) {
415
+ if (
416
+ this.beginDrag(event.target as HTMLElement, event.clientX, event.clientY)
417
+ ) {
384
418
  event.preventDefault();
385
419
  event.stopPropagation();
386
- this.downEle = ele;
387
- this.draggingId = ele.id;
388
- this.draggingIdx = this.getRowIndex(ele.id);
389
- this.draggingEle = ele;
390
-
391
- // Use getBoundingClientRect for accurate offsets and store original dimensions
392
- const rect = ele.getBoundingClientRect();
393
- this.originalElementRect = rect; // Store viewport rect for ghost positioning
394
- this.originalLayoutSize = {
395
- width: ele.offsetWidth,
396
- height: ele.offsetHeight
397
- }; // Store layout dimensions for placeholders
398
- this.xOffset = event.clientX - rect.left;
399
- this.yOffset = event.clientY - rect.top;
400
- this.yDown = event.clientY;
401
- this.xDown = event.clientX;
402
-
403
420
  document.addEventListener('mousemove', this.handleMouseMove);
404
421
  document.addEventListener('mouseup', this.handleMouseUp);
405
422
  }
406
423
  }
407
424
 
408
- private handleMouseMove(event: MouseEvent) {
425
+ /* c8 ignore start -- touch-only handlers */
426
+ private handleTouchStart(event: TouchEvent) {
427
+ const touch = event.touches[0];
428
+ if (!touch) return;
429
+ if (
430
+ this.beginDrag(event.target as HTMLElement, touch.clientX, touch.clientY)
431
+ ) {
432
+ event.stopPropagation();
433
+ document.addEventListener('touchmove', this.handleTouchMove, {
434
+ passive: false
435
+ });
436
+ document.addEventListener('touchend', this.handleTouchEnd);
437
+ document.addEventListener('touchcancel', this.handleTouchEnd);
438
+ }
439
+ }
440
+ /* c8 ignore stop */
441
+
442
+ /**
443
+ * Shared drag-move logic for both mouse and touch.
444
+ */
445
+ private processDragMove(clientX: number, clientY: number) {
409
446
  if (
410
447
  !this.ghostElement &&
411
448
  this.downEle &&
412
- (Math.abs(event.clientY - this.yDown) > DRAG_THRESHOLD ||
413
- Math.abs(event.clientX - this.xDown) > DRAG_THRESHOLD)
449
+ (Math.abs(clientY - this.yDown) > DRAG_THRESHOLD ||
450
+ Math.abs(clientX - this.xDown) > DRAG_THRESHOLD)
414
451
  ) {
415
452
  this.fireCustomEvent(CustomEventType.DragStart, {
416
453
  id: this.downEle.id
@@ -440,8 +477,8 @@ export class SortableList extends RapidElement {
440
477
  const hasAncestorScale = Math.abs(ancestorScale - 1) > 0.001;
441
478
 
442
479
  this.ghostElement.style.position = 'fixed';
443
- this.ghostElement.style.left = event.clientX - this.xOffset + 'px';
444
- this.ghostElement.style.top = event.clientY - this.yOffset + 'px';
480
+ this.ghostElement.style.left = clientX - this.xOffset + 'px';
481
+ this.ghostElement.style.top = clientY - this.yOffset + 'px';
445
482
  this.ghostElement.style.zIndex = '99999';
446
483
  this.ghostElement.style.opacity = '0.8';
447
484
 
@@ -480,12 +517,12 @@ export class SortableList extends RapidElement {
480
517
  }
481
518
 
482
519
  if (this.ghostElement) {
483
- this.ghostElement.style.left = event.clientX - this.xOffset + 'px';
484
- this.ghostElement.style.top = event.clientY - this.yOffset + 'px';
520
+ this.ghostElement.style.left = clientX - this.xOffset + 'px';
521
+ this.ghostElement.style.top = clientY - this.yOffset + 'px';
485
522
 
486
523
  // check if the drag is over the container (only if external dragging is allowed)
487
524
  const isOverContainer = this.externalDrag
488
- ? this.isMouseOverContainer(event.clientX, event.clientY)
525
+ ? this.isMouseOverContainer(clientX, clientY)
489
526
  : true; // always consider "over container" if external drag is disabled
490
527
 
491
528
  // detect transition between internal and external drag (only if allowed)
@@ -501,8 +538,8 @@ export class SortableList extends RapidElement {
501
538
 
502
539
  this.fireCustomEvent(CustomEventType.DragExternal, {
503
540
  id: this.downEle.id,
504
- mouseX: event.clientX,
505
- mouseY: event.clientY
541
+ mouseX: clientX,
542
+ mouseY: clientY
506
543
  });
507
544
  } else if (this.externalDrag && isOverContainer && this.isExternalDrag) {
508
545
  // transitioning back to internal drag
@@ -520,7 +557,7 @@ export class SortableList extends RapidElement {
520
557
 
521
558
  // only show drop placeholder and calculate drop position if internal drag
522
559
  if (!this.isExternalDrag) {
523
- const targetInfo = this.getDropTargetInfo(event.clientX, event.clientY);
560
+ const targetInfo = this.getDropTargetInfo(clientX, clientY);
524
561
  if (targetInfo) {
525
562
  const { element: targetElement, insertAfter } = targetInfo;
526
563
  const targetIdx = this.getRowIndex(targetElement.id);
@@ -537,9 +574,6 @@ export class SortableList extends RapidElement {
537
574
  dropIdx = insertAfter ? targetIdx + 1 : targetIdx;
538
575
  } else {
539
576
  // Target was originally after the drag position - moving forward
540
- // When moving the dragged element forward (i.e., to a higher index), the targetIdx is based on the current DOM,
541
- // which no longer includes the dragged element. This means all elements after the original position have shifted left by one,
542
- // so we need to subtract 1 from targetIdx to get the correct insertion index. If inserting after the target, we use targetIdx as is.
543
577
  dropIdx = insertAfter ? targetIdx : targetIdx - 1;
544
578
  }
545
579
 
@@ -560,18 +594,30 @@ export class SortableList extends RapidElement {
560
594
  // external drag - continue firing external drag events with updated position
561
595
  this.fireCustomEvent(CustomEventType.DragExternal, {
562
596
  id: this.downEle.id,
563
- mouseX: event.clientX,
564
- mouseY: event.clientY
597
+ mouseX: clientX,
598
+ mouseY: clientY
565
599
  });
566
600
  }
567
601
  }
568
602
  }
569
603
 
570
- private handleMouseUp(evt: MouseEvent) {
571
- if (this.draggingId && this.ghostElement) {
572
- evt.preventDefault();
573
- evt.stopPropagation();
604
+ private handleMouseMove(event: MouseEvent) {
605
+ this.processDragMove(event.clientX, event.clientY);
606
+ }
607
+
608
+ /* c8 ignore next 6 -- touch-only */
609
+ private handleTouchMove(event: TouchEvent) {
610
+ const touch = event.touches[0];
611
+ if (!touch) return;
612
+ event.preventDefault();
613
+ this.processDragMove(touch.clientX, touch.clientY);
614
+ }
574
615
 
616
+ /**
617
+ * Shared drag-end logic for both mouse and touch.
618
+ */
619
+ private processDragEnd(clientX: number, clientY: number) {
620
+ if (this.draggingId && this.ghostElement) {
575
621
  // Remove the ghost clone from document.body
576
622
  if (this.ghostElement) {
577
623
  this.ghostElement.remove();
@@ -610,8 +656,8 @@ export class SortableList extends RapidElement {
610
656
  this.fireCustomEvent(CustomEventType.DragStop, {
611
657
  id: this.draggingId,
612
658
  isExternal: this.isExternalDrag,
613
- mouseX: evt.clientX,
614
- mouseY: evt.clientY
659
+ mouseX: clientX,
660
+ mouseY: clientY
615
661
  });
616
662
 
617
663
  this.draggingId = null;
@@ -639,18 +685,42 @@ export class SortableList extends RapidElement {
639
685
  }, 100);
640
686
  }
641
687
  }
688
+ }
689
+
690
+ private handleMouseUp(evt: MouseEvent) {
691
+ if (this.draggingId && this.ghostElement) {
692
+ evt.preventDefault();
693
+ evt.stopPropagation();
694
+ }
695
+ this.processDragEnd(evt.clientX, evt.clientY);
642
696
  document.removeEventListener('mousemove', this.handleMouseMove);
643
697
  document.removeEventListener('mouseup', this.handleMouseUp);
644
698
  this.dispatchEvent(new Event('change'));
645
699
  }
646
700
 
701
+ /* c8 ignore start -- touch-only */
702
+ private handleTouchEnd(evt: TouchEvent) {
703
+ const touch = evt.changedTouches[0];
704
+ const clientX = touch?.clientX ?? 0;
705
+ const clientY = touch?.clientY ?? 0;
706
+ this.processDragEnd(clientX, clientY);
707
+ document.removeEventListener('touchmove', this.handleTouchMove);
708
+ document.removeEventListener('touchend', this.handleTouchEnd);
709
+ document.removeEventListener('touchcancel', this.handleTouchEnd);
710
+ this.dispatchEvent(new Event('change'));
711
+ }
712
+ /* c8 ignore stop */
713
+
647
714
  public render(): TemplateResult {
648
715
  return html`
649
716
  <div
650
717
  class="container ${this.horizontal ? 'horizontal' : ''}"
651
718
  style="gap: ${this.gap}"
652
719
  >
653
- <slot @mousedown=${this.handleMouseDown}></slot>
720
+ <slot
721
+ @mousedown=${this.handleMouseDown}
722
+ @touchstart=${this.handleTouchStart}
723
+ ></slot>
654
724
  </div>
655
725
  `;
656
726
  }
@@ -582,6 +582,7 @@ export class Simulator extends RapidElement {
582
582
  flex-wrap: wrap;
583
583
  justify-content: center;
584
584
  gap: 6px;
585
+ padding: 0 12px;
585
586
  z-index: 9;
586
587
  }
587
588
 
@@ -594,7 +595,10 @@ export class Simulator extends RapidElement {
594
595
  font-size: 11px;
595
596
  cursor: pointer;
596
597
  transition: all 0.2s ease;
597
- flex-shrink: 0;
598
+ min-width: 0;
599
+ overflow: hidden;
600
+ text-overflow: ellipsis;
601
+ white-space: nowrap;
598
602
  }
599
603
 
600
604
  .quick-reply-btn:hover:not(:disabled) {