@nyaruka/temba-components 0.137.0 → 0.138.4

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 (88) hide show
  1. package/.devcontainer/Dockerfile +0 -9
  2. package/.devcontainer/devcontainer.json +8 -3
  3. package/.github/workflows/build.yml +6 -1
  4. package/.github/workflows/cla.yml +1 -1
  5. package/.github/workflows/publish.yml +6 -1
  6. package/CHANGELOG.md +42 -0
  7. package/dist/locales/es.js +5 -5
  8. package/dist/locales/es.js.map +1 -1
  9. package/dist/locales/fr.js +5 -5
  10. package/dist/locales/fr.js.map +1 -1
  11. package/dist/locales/locale-codes.js +11 -2
  12. package/dist/locales/locale-codes.js.map +1 -1
  13. package/dist/locales/pt.js +5 -5
  14. package/dist/locales/pt.js.map +1 -1
  15. package/dist/temba-components.js +445 -278
  16. package/dist/temba-components.js.map +1 -1
  17. package/out-tsc/src/display/FloatingTab.js +16 -8
  18. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  19. package/out-tsc/src/flow/CanvasMenu.js +33 -15
  20. package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
  21. package/out-tsc/src/flow/CanvasNode.js +49 -24
  22. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  23. package/out-tsc/src/flow/Editor.js +583 -70
  24. package/out-tsc/src/flow/Editor.js.map +1 -1
  25. package/out-tsc/src/flow/NodeTypeSelector.js +13 -11
  26. package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -1
  27. package/out-tsc/src/flow/Plumber.js +110 -64
  28. package/out-tsc/src/flow/Plumber.js.map +1 -1
  29. package/out-tsc/src/flow/actions/set_contact_field.js +5 -1
  30. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  31. package/out-tsc/src/list/RunList.js +2 -1
  32. package/out-tsc/src/list/RunList.js.map +1 -1
  33. package/out-tsc/src/list/TicketList.js +2 -1
  34. package/out-tsc/src/list/TicketList.js.map +1 -1
  35. package/out-tsc/src/locales/es.js +5 -5
  36. package/out-tsc/src/locales/es.js.map +1 -1
  37. package/out-tsc/src/locales/fr.js +5 -5
  38. package/out-tsc/src/locales/fr.js.map +1 -1
  39. package/out-tsc/src/locales/locale-codes.js +11 -2
  40. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  41. package/out-tsc/src/locales/pt.js +5 -5
  42. package/out-tsc/src/locales/pt.js.map +1 -1
  43. package/out-tsc/src/simulator/Simulator.js +11 -4
  44. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  45. package/out-tsc/src/store/AppState.js +17 -2
  46. package/out-tsc/src/store/AppState.js.map +1 -1
  47. package/out-tsc/test/temba-contact-fields.test.js +3 -3
  48. package/out-tsc/test/temba-contact-fields.test.js.map +1 -1
  49. package/out-tsc/test/temba-flow-editor-node.test.js +3 -1
  50. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  51. package/out-tsc/test/temba-flow-editor-revisions.test.js +106 -0
  52. package/out-tsc/test/temba-flow-editor-revisions.test.js.map +1 -0
  53. package/out-tsc/test/temba-flow-editor.test.js +14 -10
  54. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  55. package/out-tsc/test/temba-flow-plumber-connections.test.js +7 -1
  56. package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
  57. package/out-tsc/test/temba-flow-plumber.test.js +6 -0
  58. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  59. package/out-tsc/test/temba-select.test.js +1 -0
  60. package/out-tsc/test/temba-select.test.js.map +1 -1
  61. package/package.json +1 -1
  62. package/screenshots/truth/floating-tab/gray.png +0 -0
  63. package/screenshots/truth/floating-tab/green.png +0 -0
  64. package/screenshots/truth/floating-tab/purple.png +0 -0
  65. package/screenshots/truth/node-type-selector/action-mode.png +0 -0
  66. package/screenshots/truth/node-type-selector/split-mode.png +0 -0
  67. package/src/display/FloatingTab.ts +18 -8
  68. package/src/flow/CanvasMenu.ts +38 -16
  69. package/src/flow/CanvasNode.ts +62 -29
  70. package/src/flow/Editor.ts +699 -74
  71. package/src/flow/NodeTypeSelector.ts +13 -11
  72. package/src/flow/Plumber.ts +123 -69
  73. package/src/flow/actions/set_contact_field.ts +5 -1
  74. package/src/list/RunList.ts +2 -1
  75. package/src/list/TicketList.ts +2 -1
  76. package/src/locales/es.ts +18 -13
  77. package/src/locales/fr.ts +18 -13
  78. package/src/locales/locale-codes.ts +11 -2
  79. package/src/locales/pt.ts +18 -13
  80. package/src/simulator/Simulator.ts +11 -5
  81. package/src/store/AppState.ts +18 -2
  82. package/test/temba-contact-fields.test.ts +8 -3
  83. package/test/temba-flow-editor-node.test.ts +3 -1
  84. package/test/temba-flow-editor-revisions.test.ts +134 -0
  85. package/test/temba-flow-editor.test.ts +16 -10
  86. package/test/temba-flow-plumber-connections.test.ts +7 -1
  87. package/test/temba-flow-plumber.test.ts +6 -0
  88. package/test/temba-select.test.ts +1 -0
@@ -33,6 +33,9 @@ export class CanvasNode extends RapidElement {
33
33
  @fromStore(zustand, (state: AppState) => state.languageCode)
34
34
  private languageCode!: string;
35
35
 
36
+ @fromStore(zustand, (state: AppState) => state.viewingRevision)
37
+ private viewingRevision!: boolean;
38
+
36
39
  @fromStore(zustand, (state: AppState) => state.flowDefinition)
37
40
  private flowDefinition!: any;
38
41
 
@@ -49,6 +52,8 @@ export class CanvasNode extends RapidElement {
49
52
  // Track exits that are in "removing" state
50
53
  private exitRemovalTimeouts: Map<string, number> = new Map();
51
54
 
55
+ private connectionTimeout: number | null = null;
56
+
52
57
  // Set of exit UUIDs that are in the removing state
53
58
  private exitRemovingState: Set<string> = new Set();
54
59
 
@@ -91,11 +96,8 @@ export class CanvasNode extends RapidElement {
91
96
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
92
97
  min-width: 200px;
93
98
  border-radius: var(--curvature);
94
-
95
99
  color: #333;
96
- cursor: move;
97
100
  user-select: none;
98
-
99
101
  }
100
102
 
101
103
  /* Flow start indicator */
@@ -178,7 +180,7 @@ export class CanvasNode extends RapidElement {
178
180
  opacity: 1;
179
181
  }
180
182
 
181
- .translating-hidden {
183
+ .read-only-hidden {
182
184
  visibility: hidden !important;
183
185
  pointer-events: none !important;
184
186
  }
@@ -373,6 +375,12 @@ export class CanvasNode extends RapidElement {
373
375
  .exit.connected:hover {
374
376
  background-color: var(--color-connectors, #e6e6e6);
375
377
  }
378
+
379
+ .exit.connected.read-only, .exit.connected.read-only:hover {
380
+ background-color: #fff;
381
+ pointer-events: none !important;
382
+ cursor: default;
383
+ }
376
384
 
377
385
  .exit.removing, .exit.removing:hover {
378
386
  background-color: var(--color-error);
@@ -530,23 +538,31 @@ export class CanvasNode extends RapidElement {
530
538
  if (changes.has('node')) {
531
539
  // Only proceed if plumber is available (for tests that don't set it up)
532
540
  if (this.plumber) {
533
- this.plumber.removeNodeConnections(this.node.uuid);
541
+ if (this.connectionTimeout) {
542
+ clearTimeout(this.connectionTimeout);
543
+ }
544
+
545
+ // Pass exit IDs explicitly to avoid DOM querying dependency
546
+ const exitIds = this.node.exits.map((e) => e.uuid);
547
+ this.plumber.removeNodeConnections(this.node.uuid, exitIds);
548
+
534
549
  // make our initial connections
535
- for (const exit of this.node.exits) {
536
- if (!exit.destination_uuid) {
537
- // if we have no destination, then we are a source
538
- // so make our source endpoint
550
+ // We use setTimeout to allow for DOM updates to complete before querying for exits
551
+ this.connectionTimeout = window.setTimeout(() => {
552
+ for (const exit of this.node.exits) {
539
553
  this.plumber.makeSource(exit.uuid);
540
- } else {
541
- this.plumber.connectIds(
542
- this.node.uuid,
543
- exit.uuid,
544
- exit.destination_uuid
545
- );
554
+ if (exit.destination_uuid) {
555
+ this.plumber.connectIds(
556
+ this.node.uuid,
557
+ exit.uuid,
558
+ exit.destination_uuid
559
+ );
560
+ }
546
561
  }
547
- }
548
-
549
- this.plumber.revalidate([this.node.uuid]);
562
+ // Note: revalidation is handled by plumber's processPendingConnections which calls repaintEverything
563
+ this.connectionTimeout = null;
564
+ this.plumber.revalidate([this.node.uuid]);
565
+ }, 0);
550
566
  }
551
567
 
552
568
  const ele = this.parentElement;
@@ -564,6 +580,15 @@ export class CanvasNode extends RapidElement {
564
580
  }
565
581
 
566
582
  disconnectedCallback() {
583
+ // Force cleanup of connections for this node
584
+ if (this.plumber && this.node) {
585
+ if (this.connectionTimeout) {
586
+ clearTimeout(this.connectionTimeout);
587
+ this.connectionTimeout = null;
588
+ }
589
+ this.plumber.forgetNode(this.node.uuid);
590
+ }
591
+
567
592
  // Remove the event listener when the component is removed
568
593
  super.disconnectedCallback();
569
594
 
@@ -604,12 +629,19 @@ export class CanvasNode extends RapidElement {
604
629
  // Clear the removing state
605
630
  this.exitRemovingState.clear();
606
631
  this.actionRemovingState.clear();
632
+
633
+ // only proceed if plumber is available (for tests that don't set it up)
634
+ if (this.plumber) {
635
+ this.plumber.removeNodeConnections(
636
+ this.node.uuid,
637
+ this.node.exits.map((e) => e.uuid)
638
+ );
639
+ }
607
640
  }
608
641
 
609
642
  private handleExitClick(event: MouseEvent, exit: Exit) {
610
643
  event.preventDefault();
611
644
  event.stopPropagation();
612
-
613
645
  const exitId = exit.uuid;
614
646
 
615
647
  // If exit is not connected, do nothing
@@ -1282,23 +1314,19 @@ export class CanvasNode extends RapidElement {
1282
1314
  return html`<div class="cn-title" style="background:${color}">
1283
1315
  ${this.ui?.type === 'execute_actions'
1284
1316
  ? html`<temba-icon
1285
- class="drag-handle ${this.isTranslating
1286
- ? 'translating-hidden'
1287
- : ''}"
1317
+ class="drag-handle ${this.isReadOnly() ? 'read-only-hidden' : ''}"
1288
1318
  name="sort"
1289
1319
  ></temba-icon>`
1290
1320
  : this.node?.actions?.length > 1
1291
1321
  ? html`<temba-icon
1292
- class="drag-handle ${this.isTranslating
1293
- ? 'translating-hidden'
1294
- : ''}"
1322
+ class="drag-handle ${this.isReadOnly() ? 'read-only-hidden' : ''}"
1295
1323
  name="sort"
1296
1324
  ></temba-icon>`
1297
1325
  : html`<div class="title-spacer"></div>`}
1298
1326
 
1299
1327
  <div class="name">${isRemoving ? 'Remove?' : config.name}</div>
1300
1328
  <div
1301
- class="remove-button ${this.isTranslating ? 'translating-hidden' : ''}"
1329
+ class="remove-button ${this.isReadOnly() ? 'read-only-hidden' : ''}"
1302
1330
  @click=${(e: MouseEvent) =>
1303
1331
  this.handleActionRemoveClick(e, action, index)}
1304
1332
  title="Remove action"
@@ -1332,7 +1360,7 @@ export class CanvasNode extends RapidElement {
1332
1360
  : html`${config.name}`}
1333
1361
  </div>
1334
1362
  <div
1335
- class="remove-button ${this.isTranslating ? 'translating-hidden' : ''}"
1363
+ class="remove-button ${this.isReadOnly() ? 'read-only-hidden' : ''}"
1336
1364
  @click=${(e: MouseEvent) => this.handleNodeRemoveClick(e)}
1337
1365
  title="Remove node"
1338
1366
  >
@@ -1571,13 +1599,18 @@ export class CanvasNode extends RapidElement {
1571
1599
  class=${getClasses({
1572
1600
  exit: true,
1573
1601
  connected: !!exit.destination_uuid,
1574
- removing: this.exitRemovingState.has(exit.uuid)
1602
+ removing: this.exitRemovingState.has(exit.uuid),
1603
+ 'read-only': this.isReadOnly()
1575
1604
  })}
1576
1605
  @click=${(e: MouseEvent) => this.handleExitClick(e, exit)}
1577
1606
  ></div>
1578
1607
  </div>`;
1579
1608
  }
1580
1609
 
1610
+ private isReadOnly(): boolean {
1611
+ return this.viewingRevision || this.isTranslating;
1612
+ }
1613
+
1581
1614
  public render() {
1582
1615
  if (!this.node || !this.ui) {
1583
1616
  return html`<div class="node">Loading...</div>`;
@@ -1666,7 +1699,7 @@ export class CanvasNode extends RapidElement {
1666
1699
  (exit) => this.renderExit(exit)
1667
1700
  )}
1668
1701
  </div>`}
1669
- ${this.ui.type === 'execute_actions' && !this.isTranslating
1702
+ ${this.ui.type === 'execute_actions' && !this.isReadOnly()
1670
1703
  ? html`<div
1671
1704
  class="add-action-button"
1672
1705
  @click=${(e: MouseEvent) => this.handleAddActionClick(e)}