@nyaruka/temba-components 0.142.1 → 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 (140) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/temba-components.js +953 -708
  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 +38 -38
  7. package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
  8. package/out-tsc/src/flow/CanvasNode.js +171 -17
  9. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  10. package/out-tsc/src/flow/Editor.js +491 -22
  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 +92 -28
  17. package/out-tsc/src/flow/Plumber.js.map +1 -1
  18. package/out-tsc/src/flow/StickyNote.js +63 -3
  19. package/out-tsc/src/flow/StickyNote.js.map +1 -1
  20. package/out-tsc/src/flow/actions/add_contact_urn.js +2 -6
  21. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
  22. package/out-tsc/src/flow/actions/enter_flow.js +2 -2
  23. package/out-tsc/src/flow/actions/enter_flow.js.map +1 -1
  24. package/out-tsc/src/flow/actions/say_msg.js +2 -1
  25. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  26. package/out-tsc/src/flow/actions/send_broadcast.js +2 -6
  27. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
  28. package/out-tsc/src/flow/actions/send_email.js +2 -6
  29. package/out-tsc/src/flow/actions/send_email.js.map +1 -1
  30. package/out-tsc/src/flow/actions/send_msg.js +55 -35
  31. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  32. package/out-tsc/src/flow/actions/set_contact_channel.js +2 -1
  33. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  34. package/out-tsc/src/flow/actions/set_contact_field.js +4 -5
  35. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  36. package/out-tsc/src/flow/actions/set_contact_language.js +3 -3
  37. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  38. package/out-tsc/src/flow/actions/set_contact_name.js +2 -1
  39. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
  40. package/out-tsc/src/flow/actions/set_contact_status.js +2 -1
  41. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  42. package/out-tsc/src/flow/actions/set_run_result.js +3 -3
  43. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  44. package/out-tsc/src/flow/actions/start_session.js +2 -2
  45. package/out-tsc/src/flow/actions/start_session.js.map +1 -1
  46. package/out-tsc/src/flow/nodes/split_by_llm.js +4 -5
  47. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
  48. package/out-tsc/src/flow/nodes/split_by_resthook.js +3 -8
  49. package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -1
  50. package/out-tsc/src/flow/nodes/split_by_subflow.js +4 -2
  51. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
  52. package/out-tsc/src/flow/nodes/split_by_webhook.js +25 -33
  53. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
  54. package/out-tsc/src/flow/nodes/wait_for_response.js +1 -0
  55. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  56. package/out-tsc/src/flow/types.js.map +1 -1
  57. package/out-tsc/src/flow/utils.js +66 -0
  58. package/out-tsc/src/flow/utils.js.map +1 -1
  59. package/out-tsc/src/form/FieldRenderer.js +17 -2
  60. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  61. package/out-tsc/src/interfaces.js +1 -0
  62. package/out-tsc/src/interfaces.js.map +1 -1
  63. package/out-tsc/src/list/SortableList.js +104 -43
  64. package/out-tsc/src/list/SortableList.js.map +1 -1
  65. package/out-tsc/src/simulator/Simulator.js +6 -2
  66. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  67. package/out-tsc/test/temba-canvas-menu.test.js +13 -9
  68. package/out-tsc/test/temba-canvas-menu.test.js.map +1 -1
  69. package/out-tsc/test/temba-flow-reflow.test.js.map +1 -1
  70. package/out-tsc/test/temba-node-editor.test.js +9 -10
  71. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  72. package/out-tsc/test/temba-node-type-selector.test.js +3 -3
  73. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
  74. package/out-tsc/test/temba-simulator.test.js +2 -2
  75. package/out-tsc/test/temba-simulator.test.js.map +1 -1
  76. package/package.json +1 -1
  77. package/screenshots/truth/actions/enter_flow/render/basic-flow.png +0 -0
  78. package/screenshots/truth/actions/enter_flow/render/long-flow-name.png +0 -0
  79. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  80. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  81. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  82. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  83. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  84. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  85. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  86. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  87. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  88. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  89. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  90. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  91. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  92. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  93. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  94. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  95. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  96. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  97. package/screenshots/truth/canvas-menu/open.png +0 -0
  98. package/screenshots/truth/node-type-selector/action-mode.png +0 -0
  99. package/screenshots/truth/node-type-selector/split-mode.png +0 -0
  100. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  101. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  102. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  103. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  104. package/src/Icons.ts +1 -0
  105. package/src/flow/CanvasMenu.ts +50 -43
  106. package/src/flow/CanvasNode.ts +201 -17
  107. package/src/flow/Editor.ts +585 -25
  108. package/src/flow/NodeEditor.ts +373 -10
  109. package/src/flow/NodeTypeSelector.ts +2 -0
  110. package/src/flow/Plumber.ts +104 -37
  111. package/src/flow/StickyNote.ts +76 -4
  112. package/src/flow/actions/add_contact_urn.ts +5 -6
  113. package/src/flow/actions/enter_flow.ts +2 -2
  114. package/src/flow/actions/say_msg.ts +2 -1
  115. package/src/flow/actions/send_broadcast.ts +2 -6
  116. package/src/flow/actions/send_email.ts +2 -6
  117. package/src/flow/actions/send_msg.ts +59 -38
  118. package/src/flow/actions/set_contact_channel.ts +5 -1
  119. package/src/flow/actions/set_contact_field.ts +10 -5
  120. package/src/flow/actions/set_contact_language.ts +6 -3
  121. package/src/flow/actions/set_contact_name.ts +5 -1
  122. package/src/flow/actions/set_contact_status.ts +5 -1
  123. package/src/flow/actions/set_run_result.ts +6 -3
  124. package/src/flow/actions/start_session.ts +2 -2
  125. package/src/flow/nodes/split_by_llm.ts +5 -5
  126. package/src/flow/nodes/split_by_resthook.ts +3 -8
  127. package/src/flow/nodes/split_by_subflow.ts +4 -2
  128. package/src/flow/nodes/split_by_webhook.ts +26 -34
  129. package/src/flow/nodes/wait_for_response.ts +1 -0
  130. package/src/flow/types.ts +25 -2
  131. package/src/flow/utils.ts +79 -1
  132. package/src/form/FieldRenderer.ts +32 -3
  133. package/src/interfaces.ts +1 -0
  134. package/src/list/SortableList.ts +117 -47
  135. package/src/simulator/Simulator.ts +6 -2
  136. package/test/temba-canvas-menu.test.ts +13 -9
  137. package/test/temba-flow-reflow.test.ts +4 -2
  138. package/test/temba-node-editor.test.ts +9 -10
  139. package/test/temba-node-type-selector.test.ts +3 -3
  140. package/test/temba-simulator.test.ts +2 -2
@@ -150,6 +150,25 @@ export class NodeEditor extends RapidElement {
150
150
  overflow: hidden;
151
151
  }
152
152
 
153
+ .form-group.no-border {
154
+ border: none;
155
+ }
156
+
157
+ .form-group.no-border > .form-group-header {
158
+ background: none;
159
+ border-bottom: none;
160
+ padding-left: 11px; /* 1px border + 10px padding to align with bordered groups */
161
+ }
162
+
163
+ .form-group.no-border > .form-group-header:hover {
164
+ background: none;
165
+ }
166
+
167
+ .form-group.no-border > .form-group-content {
168
+ padding-left: 0;
169
+ padding-right: 0;
170
+ }
171
+
153
172
  .form-group.has-errors {
154
173
  border-color: var(--color-error, tomato);
155
174
  }
@@ -314,6 +333,133 @@ export class NodeEditor extends RapidElement {
314
333
  align-items: center;
315
334
  }
316
335
 
336
+ /* Accordion styles */
337
+ .accordion {
338
+ border: 1px solid #e0e0e0;
339
+ border-radius: 6px;
340
+ overflow: hidden;
341
+ }
342
+
343
+ .accordion-section {
344
+ border-bottom: 1px solid #e0e0e0;
345
+ }
346
+
347
+ .accordion-section:last-child {
348
+ border-bottom: none;
349
+ }
350
+
351
+ .accordion-header {
352
+ display: flex;
353
+ align-items: center;
354
+ justify-content: space-between;
355
+ padding: 6px 10px;
356
+ cursor: pointer;
357
+ user-select: none;
358
+ background: #f8f9fa;
359
+ transition: background 0.15s ease;
360
+ }
361
+
362
+ .accordion-header:hover {
363
+ background: #f0f1f2;
364
+ }
365
+
366
+ .accordion-section.expanded > .accordion-header {
367
+ border-bottom: 1px solid #e0e0e0;
368
+ }
369
+
370
+ .accordion-title {
371
+ font-weight: 500;
372
+ font-size: 13px;
373
+ color: var(--color-label, #777);
374
+ }
375
+
376
+ .accordion-toggle-container {
377
+ position: relative;
378
+ display: flex;
379
+ align-items: center;
380
+ }
381
+
382
+ .accordion-toggle-icon {
383
+ color: #999;
384
+ transition: transform 0.2s ease, opacity 0.3s ease;
385
+ }
386
+
387
+ .accordion-toggle-icon.expanded {
388
+ transform: rotate(90deg);
389
+ }
390
+
391
+ .accordion-toggle-icon.faded {
392
+ opacity: 0;
393
+ }
394
+
395
+ .accordion-count-bubble {
396
+ border-radius: 50%;
397
+ display: flex;
398
+ align-items: center;
399
+ justify-content: center;
400
+ font-size: 10px;
401
+ font-weight: 600;
402
+ padding: 3px;
403
+ min-width: 10px;
404
+ min-height: 10px;
405
+ position: absolute;
406
+ top: 50%;
407
+ left: 50%;
408
+ transform: translate(-50%, -50%);
409
+ line-height: 0px;
410
+ opacity: 1;
411
+ transition: opacity 0.3s ease;
412
+ background: var(--color-bubble-bg, #fff);
413
+ border: 1px solid var(--color-bubble-border, #777);
414
+ color: var(--color-bubble-text, #000);
415
+ }
416
+
417
+ .accordion-count-bubble.hidden {
418
+ opacity: 0;
419
+ pointer-events: none;
420
+ }
421
+
422
+ .accordion-checkmark-icon {
423
+ position: absolute;
424
+ top: 50%;
425
+ left: 50%;
426
+ transform: translate(-50%, -50%);
427
+ opacity: 1;
428
+ transition: opacity 0.3s ease;
429
+ border-radius: 50%;
430
+ color: var(--color-bubble-text, #000);
431
+ background: var(--color-bubble-bg, #fff);
432
+ border: 1px solid var(--color-bubble-border, #777);
433
+ padding: 0.15em;
434
+ }
435
+
436
+ .accordion-checkmark-icon.hidden {
437
+ opacity: 0;
438
+ pointer-events: none;
439
+ }
440
+
441
+ .accordion-content {
442
+ padding: 8px 10px;
443
+ display: flex;
444
+ flex-direction: column;
445
+ gap: 8px;
446
+ overflow: hidden;
447
+ transition: all 0.2s ease-in-out;
448
+ opacity: 1;
449
+ }
450
+
451
+ .accordion-content.collapsed {
452
+ max-height: 0;
453
+ padding-top: 0;
454
+ padding-bottom: 0;
455
+ opacity: 0;
456
+ }
457
+
458
+ .accordion-error-icon {
459
+ color: var(--color-error, tomato);
460
+ margin-right: 6px;
461
+ }
462
+
317
463
  .gutter-fields {
318
464
  display: flex;
319
465
  flex-direction: column;
@@ -1041,14 +1187,20 @@ export class NodeEditor extends RapidElement {
1041
1187
  if (typeof item === 'object' && item.type === 'group') {
1042
1188
  const { label, collapsed, collapsible } = item;
1043
1189
  // Only update if the group is collapsible and has a function-based collapsed property
1190
+ // Skip reveal groups that have been expanded — they are one-way
1044
1191
  if (collapsible && typeof collapsed === 'function') {
1045
- const newCollapsedState = collapsed(this.formData);
1046
- // Only update if the state has changed to avoid unnecessary re-renders
1047
- if (this.groupCollapseState[label] !== newCollapsedState) {
1048
- this.groupCollapseState = {
1049
- ...this.groupCollapseState,
1050
- [label]: newCollapsedState
1051
- };
1192
+ if (item.reveal && this.groupCollapseState[label] === false) {
1193
+ // Reveal group was manually expanded don't re-collapse
1194
+ }
1195
+ else {
1196
+ const newCollapsedState = collapsed(this.formData);
1197
+ // Only update if the state has changed to avoid unnecessary re-renders
1198
+ if (this.groupCollapseState[label] !== newCollapsedState) {
1199
+ this.groupCollapseState = {
1200
+ ...this.groupCollapseState,
1201
+ [label]: newCollapsedState
1202
+ };
1203
+ }
1052
1204
  }
1053
1205
  }
1054
1206
  // Recursively check nested items
@@ -1058,6 +1210,22 @@ export class NodeEditor extends RapidElement {
1058
1210
  // Recursively check items in rows
1059
1211
  this.updateGroupCollapseStatesRecursive(item.items);
1060
1212
  }
1213
+ else if (typeof item === 'object' && item.type === 'accordion') {
1214
+ // Check each accordion section
1215
+ item.sections.forEach((section) => {
1216
+ const stateKey = `accordion:${section.label}`;
1217
+ if (typeof section.collapsed === 'function') {
1218
+ const newCollapsedState = section.collapsed(this.formData);
1219
+ if (this.groupCollapseState[stateKey] !== newCollapsedState) {
1220
+ this.groupCollapseState = {
1221
+ ...this.groupCollapseState,
1222
+ [stateKey]: newCollapsedState
1223
+ };
1224
+ }
1225
+ }
1226
+ this.updateGroupCollapseStatesRecursive(section.items);
1227
+ });
1228
+ }
1061
1229
  });
1062
1230
  }
1063
1231
  updateComputedFields(changedFieldName) {
@@ -1242,6 +1410,10 @@ export class NodeEditor extends RapidElement {
1242
1410
  const url = ((_b = (_a = picker.attachments) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.url) || '';
1243
1411
  this.handleNewFieldChange(fieldName, url);
1244
1412
  }
1413
+ else if (fieldName && config.type === 'template-editor') {
1414
+ // Special handling for template editor (manages template + template_variables)
1415
+ this.handleTemplateEditorChange(fieldName, e);
1416
+ }
1245
1417
  else {
1246
1418
  // Default handling for most field types
1247
1419
  this.handleFormFieldChange(fieldName, e);
@@ -1250,7 +1422,8 @@ export class NodeEditor extends RapidElement {
1250
1422
  showLabel: true,
1251
1423
  formData: this.formData,
1252
1424
  additionalData: {
1253
- attachments: this.formData.attachments || []
1425
+ attachments: this.formData.attachments || [],
1426
+ template_variables: this.formData.template_variables || []
1254
1427
  }
1255
1428
  });
1256
1429
  }
@@ -1298,6 +1471,20 @@ export class NodeEditor extends RapidElement {
1298
1471
  // Recursively check items in rows
1299
1472
  this.expandGroupsWithErrorsRecursive(item.items, errorFields);
1300
1473
  }
1474
+ else if (typeof item === 'object' && item.type === 'accordion') {
1475
+ // Check each accordion section for errors
1476
+ item.sections.forEach((section) => {
1477
+ const fieldsInSection = this.collectFieldsFromItems(section.items);
1478
+ const sectionHasErrors = fieldsInSection.some((fieldName) => errorFields.has(fieldName));
1479
+ if (sectionHasErrors) {
1480
+ this.groupCollapseState = {
1481
+ ...this.groupCollapseState,
1482
+ [`accordion:${section.label}`]: false
1483
+ };
1484
+ }
1485
+ this.expandGroupsWithErrorsRecursive(section.items, errorFields);
1486
+ });
1487
+ }
1301
1488
  });
1302
1489
  }
1303
1490
  renderLayoutItem(item, config, renderedFields) {
@@ -1321,6 +1508,8 @@ export class NodeEditor extends RapidElement {
1321
1508
  return this.renderRow(item, config, renderedFields);
1322
1509
  case 'group':
1323
1510
  return this.renderGroup(item, config, renderedFields);
1511
+ case 'accordion':
1512
+ return this.renderAccordion(item, config, renderedFields);
1324
1513
  case 'spacer':
1325
1514
  return html ``;
1326
1515
  case 'text':
@@ -1416,7 +1605,7 @@ export class NodeEditor extends RapidElement {
1416
1605
  }
1417
1606
  renderGroup(groupConfig, config, renderedFields) {
1418
1607
  var _a, _b;
1419
- const { label, items, collapsible = false, collapsed = false, helpText, contentPadding, getGroupValueCount } = groupConfig;
1608
+ const { label, items, collapsible = false, collapsed = false, helpText, contentPadding, bordered = true, reveal = false, getGroupValueCount } = groupConfig;
1420
1609
  // Initialize collapse state if not set
1421
1610
  if (collapsible && !(label in this.groupCollapseState)) {
1422
1611
  // Evaluate collapsed property - can be boolean or function
@@ -1429,6 +1618,10 @@ export class NodeEditor extends RapidElement {
1429
1618
  const isCollapsed = collapsible
1430
1619
  ? (_a = this.groupCollapseState[label]) !== null && _a !== void 0 ? _a : (typeof collapsed === 'function' ? collapsed(this.formData) : collapsed)
1431
1620
  : false;
1621
+ // Reveal mode: once expanded, render items directly without any group wrapper
1622
+ if (reveal && !isCollapsed) {
1623
+ return html `${items.map((item) => this.renderLayoutItem(item, config, renderedFields))}`;
1624
+ }
1432
1625
  // Check if any field in this group has errors
1433
1626
  const fieldsInGroup = this.collectFieldsFromItems(items);
1434
1627
  const groupHasErrors = fieldsInGroup.some((fieldName) => this.errors[fieldName]);
@@ -1463,7 +1656,7 @@ export class NodeEditor extends RapidElement {
1463
1656
  ? 'has-errors'
1464
1657
  : ''} ${isCollapsed ? 'collapsed' : 'expanded'} ${hasValue
1465
1658
  ? 'has-bubble'
1466
- : ''}"
1659
+ : ''} ${!bordered ? 'no-border' : ''}"
1467
1660
  >
1468
1661
  <div
1469
1662
  class="form-group-header ${collapsible ? 'clickable' : ''}"
@@ -1528,6 +1721,131 @@ export class NodeEditor extends RapidElement {
1528
1721
  </div>
1529
1722
  `;
1530
1723
  }
1724
+ renderAccordion(accordionConfig, config, renderedFields) {
1725
+ const { sections, multi = false } = accordionConfig;
1726
+ return html `
1727
+ <div class="accordion">
1728
+ ${sections.map((section) => {
1729
+ var _a, _b;
1730
+ const { label, collapsed = true, getValueCount } = section;
1731
+ const stateKey = `accordion:${label}`;
1732
+ // Initialize collapse state if not set
1733
+ if (!(stateKey in this.groupCollapseState)) {
1734
+ const initialCollapsed = typeof collapsed === 'function'
1735
+ ? collapsed(this.formData)
1736
+ : collapsed;
1737
+ this.groupCollapseState = {
1738
+ ...this.groupCollapseState,
1739
+ [stateKey]: initialCollapsed
1740
+ };
1741
+ }
1742
+ const isCollapsed = (_a = this.groupCollapseState[stateKey]) !== null && _a !== void 0 ? _a : true;
1743
+ const isHovered = (_b = this.groupHoverState[stateKey]) !== null && _b !== void 0 ? _b : false;
1744
+ // Check for errors in this section
1745
+ const fieldsInSection = this.collectFieldsFromItems(section.items);
1746
+ const sectionHasErrors = fieldsInSection.some((fieldName) => this.errors[fieldName]);
1747
+ // Value count / checkmark display
1748
+ let valueCount = 0;
1749
+ let showBubble = false;
1750
+ let showCheckmark = false;
1751
+ let hasValue = false;
1752
+ if (getValueCount) {
1753
+ try {
1754
+ const result = getValueCount(this.formData);
1755
+ if (typeof result === 'boolean') {
1756
+ hasValue = result;
1757
+ showCheckmark = result && isCollapsed && !isHovered;
1758
+ }
1759
+ else if (typeof result === 'number') {
1760
+ valueCount = result;
1761
+ hasValue = valueCount > 0;
1762
+ showBubble = valueCount > 0 && isCollapsed && !isHovered;
1763
+ }
1764
+ }
1765
+ catch (error) {
1766
+ // ignore
1767
+ }
1768
+ }
1769
+ return html `
1770
+ <div
1771
+ class="accordion-section ${isCollapsed
1772
+ ? 'collapsed'
1773
+ : 'expanded'} ${hasValue ? 'has-value' : ''}"
1774
+ >
1775
+ <div
1776
+ class="accordion-header"
1777
+ @click=${() => this.handleAccordionToggle(stateKey, sections, multi)}
1778
+ @mouseenter=${() => this.handleGroupMouseEnter(stateKey)}
1779
+ @mouseleave=${() => this.handleGroupMouseLeave(stateKey)}
1780
+ >
1781
+ <div class="accordion-title">${label}</div>
1782
+ ${sectionHasErrors
1783
+ ? html `<temba-icon
1784
+ name="alert_warning"
1785
+ class="accordion-error-icon"
1786
+ size="1.2"
1787
+ ></temba-icon>`
1788
+ : html `<div class="accordion-toggle-container">
1789
+ <temba-icon
1790
+ name="arrow_right"
1791
+ size="1.2"
1792
+ class="accordion-toggle-icon ${isCollapsed
1793
+ ? 'collapsed'
1794
+ : 'expanded'} ${showBubble || showCheckmark
1795
+ ? 'faded'
1796
+ : ''}"
1797
+ ></temba-icon>
1798
+ ${showCheckmark
1799
+ ? html `<temba-icon
1800
+ name="check"
1801
+ size="0.8"
1802
+ class="accordion-checkmark-icon"
1803
+ ></temba-icon>`
1804
+ : showBubble
1805
+ ? html `<div
1806
+ class="accordion-count-bubble ${!showBubble
1807
+ ? 'hidden'
1808
+ : ''}"
1809
+ >
1810
+ ${valueCount}
1811
+ </div>`
1812
+ : ''}
1813
+ </div>`}
1814
+ </div>
1815
+ <div
1816
+ class="accordion-content ${isCollapsed
1817
+ ? 'collapsed'
1818
+ : 'expanded'}"
1819
+ >
1820
+ ${section.items.map((item) => this.renderLayoutItem(item, config, renderedFields))}
1821
+ </div>
1822
+ </div>
1823
+ `;
1824
+ })}
1825
+ </div>
1826
+ `;
1827
+ }
1828
+ handleAccordionToggle(stateKey, sections, multi) {
1829
+ var _a;
1830
+ const isCurrentlyCollapsed = (_a = this.groupCollapseState[stateKey]) !== null && _a !== void 0 ? _a : true;
1831
+ if (multi) {
1832
+ // Multi mode: just toggle this section
1833
+ this.groupCollapseState = {
1834
+ ...this.groupCollapseState,
1835
+ [stateKey]: !isCurrentlyCollapsed
1836
+ };
1837
+ }
1838
+ else {
1839
+ // Single mode: collapse all other sections, toggle this one
1840
+ const newState = { ...this.groupCollapseState };
1841
+ sections.forEach((section) => {
1842
+ const key = `accordion:${section.label}`;
1843
+ newState[key] = true; // collapse all
1844
+ });
1845
+ newState[stateKey] = !isCurrentlyCollapsed; // toggle clicked
1846
+ this.groupCollapseState = newState;
1847
+ }
1848
+ }
1531
1849
  collectFieldsFromItems(items) {
1532
1850
  const fields = [];
1533
1851
  items.forEach((item) => {
@@ -1543,6 +1861,11 @@ export class NodeEditor extends RapidElement {
1543
1861
  else if (item.type === 'group') {
1544
1862
  fields.push(...this.collectFieldsFromItems(item.items));
1545
1863
  }
1864
+ else if (item.type === 'accordion') {
1865
+ item.sections.forEach((section) => {
1866
+ fields.push(...this.collectFieldsFromItems(section.items));
1867
+ });
1868
+ }
1546
1869
  });
1547
1870
  return fields;
1548
1871
  }
@@ -1570,6 +1893,19 @@ export class NodeEditor extends RapidElement {
1570
1893
  // Trigger re-render
1571
1894
  this.requestUpdate();
1572
1895
  }
1896
+ handleTemplateEditorChange(fieldName, event) {
1897
+ const customEvent = event;
1898
+ const detail = customEvent.detail;
1899
+ this.formData = {
1900
+ ...this.formData,
1901
+ [fieldName]: detail.template
1902
+ ? { uuid: detail.template.uuid, name: detail.template.name }
1903
+ : null,
1904
+ template_variables: detail.variables || []
1905
+ };
1906
+ this.updateGroupCollapseStates();
1907
+ this.requestUpdate();
1908
+ }
1573
1909
  handleMessageEditorChange(fieldName, event) {
1574
1910
  const target = event.target;
1575
1911
  // Update both text and attachments from the message editor