@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
@@ -9,7 +9,8 @@ import {
9
9
  MessageEditorFieldConfig,
10
10
  KeyValueFieldConfig,
11
11
  ArrayFieldConfig,
12
- MediaFieldConfig
12
+ MediaFieldConfig,
13
+ TemplateEditorFieldConfig
13
14
  } from '../flow/types';
14
15
  import { Attachment } from '../interfaces';
15
16
  import { DEFAULT_MEDIA_ENDPOINT } from '../utils';
@@ -101,6 +102,14 @@ export class FieldRenderer {
101
102
  context
102
103
  );
103
104
 
105
+ case 'template-editor':
106
+ return FieldRenderer.renderTemplateEditor(
107
+ fieldName,
108
+ config as TemplateEditorFieldConfig,
109
+ value,
110
+ context
111
+ );
112
+
104
113
  default:
105
114
  return html`<div>Unsupported field type: ${(config as any).type}</div>`;
106
115
  }
@@ -129,7 +138,7 @@ export class FieldRenderer {
129
138
  .errors="${errors}"
130
139
  .value="${value || ''}"
131
140
  placeholder="${config.placeholder || ''}"
132
- expressions="session"
141
+ session
133
142
  .helpText="${config.helpText || ''}"
134
143
  class="${extraClasses}"
135
144
  style="${style}"
@@ -181,7 +190,7 @@ export class FieldRenderer {
181
190
  .value="${value || ''}"
182
191
  placeholder="${config.placeholder || ''}"
183
192
  textarea
184
- expressions="session"
193
+ session
185
194
  .helpText="${config.helpText || ''}"
186
195
  class="${extraClasses}"
187
196
  style="${combinedStyle}"
@@ -462,6 +471,7 @@ export class FieldRenderer {
462
471
  ?autogrow="${config.autogrow}"
463
472
  ?gsm="${config.gsm}"
464
473
  ?disableCompletion="${config.disableCompletion}"
474
+ session
465
475
  counter="${config.counter || ''}"
466
476
  accept="${config.accept || ''}"
467
477
  endpoint="${config.endpoint || ''}"
@@ -472,6 +482,25 @@ export class FieldRenderer {
472
482
  @change="${onChange || (() => {})}"
473
483
  ></temba-message-editor>`;
474
484
  }
485
+
486
+ private static renderTemplateEditor(
487
+ _fieldName: string,
488
+ config: TemplateEditorFieldConfig,
489
+ value: any,
490
+ context: FieldRenderContext
491
+ ): TemplateResult {
492
+ const { onChange, additionalData = {} } = context;
493
+ const templateUuid = value?.uuid || '';
494
+ const variables = JSON.stringify(additionalData.template_variables || []);
495
+
496
+ return html`<temba-template-editor
497
+ url="${config.endpoint || '/api/internal/templates.json'}"
498
+ template="${templateUuid}"
499
+ variables="${variables}"
500
+ @temba-context-changed="${onChange || (() => {})}"
501
+ @temba-content-changed="${onChange || (() => {})}"
502
+ ></temba-template-editor>`;
503
+ }
475
504
  }
476
505
 
477
506
  export interface FieldRenderContext {
package/src/interfaces.ts CHANGED
@@ -305,5 +305,6 @@ export enum CustomEventType {
305
305
  NodeEditCancelled = 'temba-node-edit-cancelled',
306
306
  FollowSimulation = 'temba-follow-simulation',
307
307
  ContactClicked = 'temba-contact-clicked',
308
+ FlowClicked = 'temba-flow-clicked',
308
309
  ShowIssue = 'temba-show-issue'
309
310
  }
@@ -1944,7 +1944,7 @@ export class Simulator extends RapidElement {
1944
1944
  </button>
1945
1945
 
1946
1946
  <button class="option-btn" @click=${this.handleReset} title="Reset">
1947
- <temba-icon name="delete" size="1.5"></temba-icon>
1947
+ <temba-icon name="refresh" size="1.5"></temba-icon>
1948
1948
  </button>
1949
1949
  </div>
1950
1950
  </div>
@@ -48,19 +48,21 @@ describe('temba-canvas-menu', () => {
48
48
  await assertScreenshot('canvas-menu/open', getClip(menu));
49
49
  });
50
50
 
51
- it('has three menu items', async () => {
51
+ it('has five menu items', async () => {
52
52
  const menu = await createCanvasMenu();
53
53
  menu.show(100, 100, { x: 50, y: 50 });
54
54
  await menu.updateComplete;
55
55
 
56
56
  const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');
57
- expect(menuItems?.length).to.equal(3);
57
+ expect(menuItems?.length).to.equal(5);
58
58
 
59
59
  // check menu item titles
60
60
  const titles = Array.from(menuItems || []).map(
61
61
  (item) => item.querySelector('.menu-item-title')?.textContent
62
62
  );
63
63
  expect(titles).to.deep.equal([
64
+ 'Send Message',
65
+ 'Wait for Response',
64
66
  'Add Action',
65
67
  'Add Split',
66
68
  'Add Sticky Note'
@@ -93,9 +95,9 @@ describe('temba-canvas-menu', () => {
93
95
  selectionDetail = event.detail;
94
96
  });
95
97
 
96
- // click on sticky note option (now the third item)
98
+ // click on sticky note option (now the fifth item)
97
99
  const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');
98
- const stickyItem = menuItems?.[2] as HTMLElement;
100
+ const stickyItem = menuItems?.[4] as HTMLElement;
99
101
  stickyItem.click();
100
102
  await menu.updateComplete;
101
103
 
@@ -113,12 +115,14 @@ describe('temba-canvas-menu', () => {
113
115
  await menu.updateComplete;
114
116
 
115
117
  const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');
116
- expect(menuItems?.length).to.equal(4);
118
+ expect(menuItems?.length).to.equal(6);
117
119
 
118
120
  const titles = Array.from(menuItems || []).map(
119
121
  (item) => item.querySelector('.menu-item-title')?.textContent
120
122
  );
121
123
  expect(titles).to.deep.equal([
124
+ 'Send Message',
125
+ 'Wait for Response',
122
126
  'Add Action',
123
127
  'Add Split',
124
128
  'Add Sticky Note',
@@ -137,7 +141,7 @@ describe('temba-canvas-menu', () => {
137
141
  });
138
142
 
139
143
  const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');
140
- const reflowItem = menuItems?.[3] as HTMLElement;
144
+ const reflowItem = menuItems?.[5] as HTMLElement;
141
145
  reflowItem.click();
142
146
  await menu.updateComplete;
143
147
 
@@ -154,7 +158,7 @@ describe('temba-canvas-menu', () => {
154
158
  await menu.updateComplete;
155
159
 
156
160
  const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');
157
- expect(menuItems?.length).to.equal(3);
161
+ expect(menuItems?.length).to.equal(5);
158
162
 
159
163
  const titles = Array.from(menuItems || []).map(
160
164
  (item) => item.querySelector('.menu-item-title')?.textContent
@@ -200,8 +204,8 @@ describe('temba-canvas-menu', () => {
200
204
  });
201
205
 
202
206
  const menuItems = menu.shadowRoot?.querySelectorAll('.menu-item');
203
- const actionItem = menuItems?.[0] as HTMLElement;
204
- actionItem.click();
207
+ const sendMsgItem = menuItems?.[0] as HTMLElement;
208
+ sendMsgItem.click();
205
209
  await menu.updateComplete;
206
210
 
207
211
  expect(selectionFired).to.be.true;
@@ -375,8 +375,10 @@ describe('Reflow Layout', () => {
375
375
  ),
376
376
  ...childIds.map((id) => makeNode(id, []))
377
377
  ];
378
- const nodeUIs: Record<string, { position: { left: number; top: number } }> =
379
- { A: { position: { left: 0, top: 0 } } };
378
+ const nodeUIs: Record<
379
+ string,
380
+ { position: { left: number; top: number } }
381
+ > = { A: { position: { left: 0, top: 0 } } };
380
382
  childIds.forEach((id, i) => {
381
383
  nodeUIs[id] = { position: { left: i * 260, top: 200 } };
382
384
  });
@@ -343,7 +343,7 @@ describe('temba-node-editor', () => {
343
343
  expect(shadowRoot).to.not.be.null;
344
344
  });
345
345
 
346
- it('displays bubble count for group value counts', async () => {
346
+ it('displays bubble count for accordion value counts', async () => {
347
347
  const action = {
348
348
  uuid: 'test-action-uuid',
349
349
  type: 'send_msg',
@@ -362,11 +362,11 @@ describe('temba-node-editor', () => {
362
362
  await new Promise((resolve) => setTimeout(resolve, 200));
363
363
  await el.updateComplete;
364
364
 
365
- // Check that bubble counts are displayed
365
+ // Check that bubble counts are displayed in accordion sections
366
366
  const shadowRoot = el.shadowRoot;
367
- const bubbles = shadowRoot.querySelectorAll('.group-count-bubble');
367
+ const bubbles = shadowRoot.querySelectorAll('.accordion-count-bubble');
368
368
 
369
- // Should have bubbles for groups with values
369
+ // Should have bubbles for sections with values
370
370
  expect(bubbles.length).to.be.greaterThan(0);
371
371
 
372
372
  // Check specific bubble values (trim to handle whitespace in rendered text)
@@ -374,12 +374,11 @@ describe('temba-node-editor', () => {
374
374
  bubble.textContent?.trim()
375
375
  );
376
376
 
377
- // Runtime attachments group should show bubble when collapsed and has values
377
+ // Runtime attachments section should show bubble when collapsed and has values
378
378
  expect(bubbleTexts).to.include('2'); // 2 runtime attachments
379
- // Note: Quick replies group auto-expands when it has content, so no bubble is shown
380
379
  });
381
380
 
382
- it('shows arrow when group has no values', async () => {
381
+ it('shows arrow when accordion section has no values', async () => {
383
382
  const action = {
384
383
  uuid: 'test-action-uuid',
385
384
  type: 'send_msg',
@@ -399,13 +398,13 @@ describe('temba-node-editor', () => {
399
398
 
400
399
  // Check that arrows are displayed instead of bubbles
401
400
  const shadowRoot = el.shadowRoot;
402
- const bubbles = shadowRoot.querySelectorAll('.group-count-bubble');
403
- const arrows = shadowRoot.querySelectorAll('.group-toggle-icon');
401
+ const bubbles = shadowRoot.querySelectorAll('.accordion-count-bubble');
402
+ const arrows = shadowRoot.querySelectorAll('.accordion-toggle-icon');
404
403
 
405
404
  // Should have no bubbles when counts are 0
406
405
  expect(bubbles.length).to.equal(0);
407
406
 
408
- // Should have arrows for collapsible groups
407
+ // Should have arrows for accordion sections
409
408
  expect(arrows.length).to.be.greaterThan(0);
410
409
  });
411
410
 
@@ -186,7 +186,7 @@ describe('temba-node-type-selector', () => {
186
186
  expect(titles).to.not.include('Play Recording');
187
187
  });
188
188
 
189
- it('filters splits by flow type - message flow should show wait for response', async () => {
189
+ it('filters splits by flow type - message flow should not show wait for response in split dialog', async () => {
190
190
  const selector = await createSelector();
191
191
  selector.flowType = 'message';
192
192
  await selector.updateComplete;
@@ -199,8 +199,8 @@ describe('temba-node-type-selector', () => {
199
199
  item.textContent?.trim()
200
200
  );
201
201
 
202
- // message flow should have Wait for Response
203
- expect(titles).to.include('Wait for Response');
202
+ // Wait for Response is now promoted to the context menu, not in split dialog
203
+ expect(titles).to.not.include('Wait for Response');
204
204
  });
205
205
 
206
206
  it('filters splits by flow type - voice flow should not show wait for response', async () => {
@@ -734,12 +734,12 @@ describe('temba-simulator', () => {
734
734
  // mock the start response for reset
735
735
  mockSimulatorStart();
736
736
 
737
- // click the reset button (has delete icon)
737
+ // click the reset button (has refresh icon)
738
738
  const optionButtons = Array.from(
739
739
  simulator.shadowRoot.querySelectorAll('.option-btn')
740
740
  );
741
741
  const resetButton = optionButtons.find((btn) =>
742
- btn.querySelector('temba-icon[name="delete"]')
742
+ btn.querySelector('temba-icon[name="refresh"]')
743
743
  ) as HTMLElement;
744
744
  expect(resetButton).to.exist;
745
745
  resetButton.click();