@nyaruka/temba-components 0.142.0 → 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 (137) hide show
  1. package/CHANGELOG.md +18 -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/src/utils.js +5 -12
  64. package/out-tsc/src/utils.js.map +1 -1
  65. package/out-tsc/test/nodes/split_by_run_result.test.js +1 -2
  66. package/out-tsc/test/nodes/split_by_run_result.test.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 +2 -2
  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/multiline-text-with-replies.png +0 -0
  88. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  89. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  90. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  91. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  92. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  93. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  94. package/screenshots/truth/canvas-menu/open.png +0 -0
  95. package/screenshots/truth/node-type-selector/action-mode.png +0 -0
  96. package/screenshots/truth/node-type-selector/split-mode.png +0 -0
  97. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  98. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  99. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  100. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  101. package/src/Icons.ts +1 -0
  102. package/src/flow/CanvasMenu.ts +38 -39
  103. package/src/flow/CanvasNode.ts +16 -8
  104. package/src/flow/Editor.ts +33 -6
  105. package/src/flow/NodeEditor.ts +373 -10
  106. package/src/flow/NodeTypeSelector.ts +2 -0
  107. package/src/flow/Plumber.ts +3 -1
  108. package/src/flow/actions/add_contact_urn.ts +5 -6
  109. package/src/flow/actions/enter_flow.ts +2 -2
  110. package/src/flow/actions/say_msg.ts +2 -1
  111. package/src/flow/actions/send_broadcast.ts +2 -6
  112. package/src/flow/actions/send_email.ts +2 -6
  113. package/src/flow/actions/send_msg.ts +56 -38
  114. package/src/flow/actions/set_contact_channel.ts +5 -1
  115. package/src/flow/actions/set_contact_field.ts +10 -5
  116. package/src/flow/actions/set_contact_language.ts +6 -3
  117. package/src/flow/actions/set_contact_name.ts +5 -1
  118. package/src/flow/actions/set_contact_status.ts +5 -1
  119. package/src/flow/actions/set_run_result.ts +6 -3
  120. package/src/flow/actions/start_session.ts +2 -2
  121. package/src/flow/nodes/split_by_llm.ts +5 -5
  122. package/src/flow/nodes/split_by_resthook.ts +3 -8
  123. package/src/flow/nodes/split_by_subflow.ts +2 -2
  124. package/src/flow/nodes/split_by_webhook.ts +26 -34
  125. package/src/flow/nodes/wait_for_response.ts +1 -0
  126. package/src/flow/types.ts +25 -2
  127. package/src/flow/utils.ts +81 -1
  128. package/src/form/FieldRenderer.ts +32 -3
  129. package/src/interfaces.ts +1 -0
  130. package/src/simulator/Simulator.ts +1 -1
  131. package/src/utils.ts +5 -12
  132. package/test/nodes/split_by_run_result.test.ts +1 -2
  133. package/test/temba-canvas-menu.test.ts +13 -9
  134. package/test/temba-flow-reflow.test.ts +4 -2
  135. package/test/temba-node-editor.test.ts +9 -10
  136. package/test/temba-node-type-selector.test.ts +3 -3
  137. 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,135 @@ 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:
385
+ transform 0.2s ease,
386
+ opacity 0.3s ease;
387
+ }
388
+
389
+ .accordion-toggle-icon.expanded {
390
+ transform: rotate(90deg);
391
+ }
392
+
393
+ .accordion-toggle-icon.faded {
394
+ opacity: 0;
395
+ }
396
+
397
+ .accordion-count-bubble {
398
+ border-radius: 50%;
399
+ display: flex;
400
+ align-items: center;
401
+ justify-content: center;
402
+ font-size: 10px;
403
+ font-weight: 600;
404
+ padding: 3px;
405
+ min-width: 10px;
406
+ min-height: 10px;
407
+ position: absolute;
408
+ top: 50%;
409
+ left: 50%;
410
+ transform: translate(-50%, -50%);
411
+ line-height: 0px;
412
+ opacity: 1;
413
+ transition: opacity 0.3s ease;
414
+ background: var(--color-bubble-bg, #fff);
415
+ border: 1px solid var(--color-bubble-border, #777);
416
+ color: var(--color-bubble-text, #000);
417
+ }
418
+
419
+ .accordion-count-bubble.hidden {
420
+ opacity: 0;
421
+ pointer-events: none;
422
+ }
423
+
424
+ .accordion-checkmark-icon {
425
+ position: absolute;
426
+ top: 50%;
427
+ left: 50%;
428
+ transform: translate(-50%, -50%);
429
+ opacity: 1;
430
+ transition: opacity 0.3s ease;
431
+ border-radius: 50%;
432
+ color: var(--color-bubble-text, #000);
433
+ background: var(--color-bubble-bg, #fff);
434
+ border: 1px solid var(--color-bubble-border, #777);
435
+ padding: 0.15em;
436
+ }
437
+
438
+ .accordion-checkmark-icon.hidden {
439
+ opacity: 0;
440
+ pointer-events: none;
441
+ }
442
+
443
+ .accordion-content {
444
+ padding: 8px 10px;
445
+ display: flex;
446
+ flex-direction: column;
447
+ gap: 8px;
448
+ overflow: hidden;
449
+ transition: all 0.2s ease-in-out;
450
+ opacity: 1;
451
+ }
452
+
453
+ .accordion-content.collapsed {
454
+ max-height: 0;
455
+ padding-top: 0;
456
+ padding-bottom: 0;
457
+ opacity: 0;
458
+ }
459
+
460
+ .accordion-error-icon {
461
+ color: var(--color-error, tomato);
462
+ margin-right: 6px;
463
+ }
464
+
317
465
  .gutter-fields {
318
466
  display: flex;
319
467
  flex-direction: column;
@@ -1041,14 +1189,20 @@ export class NodeEditor extends RapidElement {
1041
1189
  if (typeof item === 'object' && item.type === 'group') {
1042
1190
  const { label, collapsed, collapsible } = item;
1043
1191
  // Only update if the group is collapsible and has a function-based collapsed property
1192
+ // Skip reveal groups that have been expanded — they are one-way
1044
1193
  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
- };
1194
+ if (item.reveal && this.groupCollapseState[label] === false) {
1195
+ // Reveal group was manually expanded don't re-collapse
1196
+ }
1197
+ else {
1198
+ const newCollapsedState = collapsed(this.formData);
1199
+ // Only update if the state has changed to avoid unnecessary re-renders
1200
+ if (this.groupCollapseState[label] !== newCollapsedState) {
1201
+ this.groupCollapseState = {
1202
+ ...this.groupCollapseState,
1203
+ [label]: newCollapsedState
1204
+ };
1205
+ }
1052
1206
  }
1053
1207
  }
1054
1208
  // Recursively check nested items
@@ -1058,6 +1212,22 @@ export class NodeEditor extends RapidElement {
1058
1212
  // Recursively check items in rows
1059
1213
  this.updateGroupCollapseStatesRecursive(item.items);
1060
1214
  }
1215
+ else if (typeof item === 'object' && item.type === 'accordion') {
1216
+ // Check each accordion section
1217
+ item.sections.forEach((section) => {
1218
+ const stateKey = `accordion:${section.label}`;
1219
+ if (typeof section.collapsed === 'function') {
1220
+ const newCollapsedState = section.collapsed(this.formData);
1221
+ if (this.groupCollapseState[stateKey] !== newCollapsedState) {
1222
+ this.groupCollapseState = {
1223
+ ...this.groupCollapseState,
1224
+ [stateKey]: newCollapsedState
1225
+ };
1226
+ }
1227
+ }
1228
+ this.updateGroupCollapseStatesRecursive(section.items);
1229
+ });
1230
+ }
1061
1231
  });
1062
1232
  }
1063
1233
  updateComputedFields(changedFieldName) {
@@ -1242,6 +1412,10 @@ export class NodeEditor extends RapidElement {
1242
1412
  const url = ((_b = (_a = picker.attachments) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.url) || '';
1243
1413
  this.handleNewFieldChange(fieldName, url);
1244
1414
  }
1415
+ else if (fieldName && config.type === 'template-editor') {
1416
+ // Special handling for template editor (manages template + template_variables)
1417
+ this.handleTemplateEditorChange(fieldName, e);
1418
+ }
1245
1419
  else {
1246
1420
  // Default handling for most field types
1247
1421
  this.handleFormFieldChange(fieldName, e);
@@ -1250,7 +1424,8 @@ export class NodeEditor extends RapidElement {
1250
1424
  showLabel: true,
1251
1425
  formData: this.formData,
1252
1426
  additionalData: {
1253
- attachments: this.formData.attachments || []
1427
+ attachments: this.formData.attachments || [],
1428
+ template_variables: this.formData.template_variables || []
1254
1429
  }
1255
1430
  });
1256
1431
  }
@@ -1298,6 +1473,20 @@ export class NodeEditor extends RapidElement {
1298
1473
  // Recursively check items in rows
1299
1474
  this.expandGroupsWithErrorsRecursive(item.items, errorFields);
1300
1475
  }
1476
+ else if (typeof item === 'object' && item.type === 'accordion') {
1477
+ // Check each accordion section for errors
1478
+ item.sections.forEach((section) => {
1479
+ const fieldsInSection = this.collectFieldsFromItems(section.items);
1480
+ const sectionHasErrors = fieldsInSection.some((fieldName) => errorFields.has(fieldName));
1481
+ if (sectionHasErrors) {
1482
+ this.groupCollapseState = {
1483
+ ...this.groupCollapseState,
1484
+ [`accordion:${section.label}`]: false
1485
+ };
1486
+ }
1487
+ this.expandGroupsWithErrorsRecursive(section.items, errorFields);
1488
+ });
1489
+ }
1301
1490
  });
1302
1491
  }
1303
1492
  renderLayoutItem(item, config, renderedFields) {
@@ -1321,6 +1510,8 @@ export class NodeEditor extends RapidElement {
1321
1510
  return this.renderRow(item, config, renderedFields);
1322
1511
  case 'group':
1323
1512
  return this.renderGroup(item, config, renderedFields);
1513
+ case 'accordion':
1514
+ return this.renderAccordion(item, config, renderedFields);
1324
1515
  case 'spacer':
1325
1516
  return html ``;
1326
1517
  case 'text':
@@ -1416,7 +1607,7 @@ export class NodeEditor extends RapidElement {
1416
1607
  }
1417
1608
  renderGroup(groupConfig, config, renderedFields) {
1418
1609
  var _a, _b;
1419
- const { label, items, collapsible = false, collapsed = false, helpText, contentPadding, getGroupValueCount } = groupConfig;
1610
+ const { label, items, collapsible = false, collapsed = false, helpText, contentPadding, bordered = true, reveal = false, getGroupValueCount } = groupConfig;
1420
1611
  // Initialize collapse state if not set
1421
1612
  if (collapsible && !(label in this.groupCollapseState)) {
1422
1613
  // Evaluate collapsed property - can be boolean or function
@@ -1429,6 +1620,10 @@ export class NodeEditor extends RapidElement {
1429
1620
  const isCollapsed = collapsible
1430
1621
  ? (_a = this.groupCollapseState[label]) !== null && _a !== void 0 ? _a : (typeof collapsed === 'function' ? collapsed(this.formData) : collapsed)
1431
1622
  : false;
1623
+ // Reveal mode: once expanded, render items directly without any group wrapper
1624
+ if (reveal && !isCollapsed) {
1625
+ return html `${items.map((item) => this.renderLayoutItem(item, config, renderedFields))}`;
1626
+ }
1432
1627
  // Check if any field in this group has errors
1433
1628
  const fieldsInGroup = this.collectFieldsFromItems(items);
1434
1629
  const groupHasErrors = fieldsInGroup.some((fieldName) => this.errors[fieldName]);
@@ -1463,7 +1658,7 @@ export class NodeEditor extends RapidElement {
1463
1658
  ? 'has-errors'
1464
1659
  : ''} ${isCollapsed ? 'collapsed' : 'expanded'} ${hasValue
1465
1660
  ? 'has-bubble'
1466
- : ''}"
1661
+ : ''} ${!bordered ? 'no-border' : ''}"
1467
1662
  >
1468
1663
  <div
1469
1664
  class="form-group-header ${collapsible ? 'clickable' : ''}"
@@ -1528,6 +1723,129 @@ export class NodeEditor extends RapidElement {
1528
1723
  </div>
1529
1724
  `;
1530
1725
  }
1726
+ renderAccordion(accordionConfig, config, renderedFields) {
1727
+ const { sections, multi = false } = accordionConfig;
1728
+ return html `
1729
+ <div class="accordion">
1730
+ ${sections.map((section) => {
1731
+ var _a, _b;
1732
+ const { label, collapsed = true, getValueCount } = section;
1733
+ const stateKey = `accordion:${label}`;
1734
+ // Initialize collapse state if not set
1735
+ if (!(stateKey in this.groupCollapseState)) {
1736
+ const initialCollapsed = typeof collapsed === 'function'
1737
+ ? collapsed(this.formData)
1738
+ : collapsed;
1739
+ this.groupCollapseState = {
1740
+ ...this.groupCollapseState,
1741
+ [stateKey]: initialCollapsed
1742
+ };
1743
+ }
1744
+ const isCollapsed = (_a = this.groupCollapseState[stateKey]) !== null && _a !== void 0 ? _a : true;
1745
+ const isHovered = (_b = this.groupHoverState[stateKey]) !== null && _b !== void 0 ? _b : false;
1746
+ // Check for errors in this section
1747
+ const fieldsInSection = this.collectFieldsFromItems(section.items);
1748
+ const sectionHasErrors = fieldsInSection.some((fieldName) => this.errors[fieldName]);
1749
+ // Value count / checkmark display
1750
+ let valueCount = 0;
1751
+ let showBubble = false;
1752
+ let showCheckmark = false;
1753
+ let hasValue = false;
1754
+ if (getValueCount) {
1755
+ try {
1756
+ const result = getValueCount(this.formData);
1757
+ if (typeof result === 'boolean') {
1758
+ hasValue = result;
1759
+ showCheckmark = result && isCollapsed && !isHovered;
1760
+ }
1761
+ else if (typeof result === 'number') {
1762
+ valueCount = result;
1763
+ hasValue = valueCount > 0;
1764
+ showBubble = valueCount > 0 && isCollapsed && !isHovered;
1765
+ }
1766
+ }
1767
+ catch (error) {
1768
+ // ignore
1769
+ }
1770
+ }
1771
+ return html `
1772
+ <div
1773
+ class="accordion-section ${isCollapsed
1774
+ ? 'collapsed'
1775
+ : 'expanded'} ${hasValue ? 'has-value' : ''}"
1776
+ >
1777
+ <div
1778
+ class="accordion-header"
1779
+ @click=${() => this.handleAccordionToggle(stateKey, sections, multi)}
1780
+ @mouseenter=${() => this.handleGroupMouseEnter(stateKey)}
1781
+ @mouseleave=${() => this.handleGroupMouseLeave(stateKey)}
1782
+ >
1783
+ <div class="accordion-title">${label}</div>
1784
+ ${sectionHasErrors
1785
+ ? html `<temba-icon
1786
+ name="alert_warning"
1787
+ class="accordion-error-icon"
1788
+ size="1.2"
1789
+ ></temba-icon>`
1790
+ : html `<div class="accordion-toggle-container">
1791
+ <temba-icon
1792
+ name="arrow_right"
1793
+ size="1.2"
1794
+ class="accordion-toggle-icon ${isCollapsed
1795
+ ? 'collapsed'
1796
+ : 'expanded'} ${showBubble || showCheckmark
1797
+ ? 'faded'
1798
+ : ''}"
1799
+ ></temba-icon>
1800
+ ${showCheckmark
1801
+ ? html `<temba-icon
1802
+ name="check"
1803
+ size="0.8"
1804
+ class="accordion-checkmark-icon"
1805
+ ></temba-icon>`
1806
+ : showBubble
1807
+ ? html `<div
1808
+ class="accordion-count-bubble ${!showBubble
1809
+ ? 'hidden'
1810
+ : ''}"
1811
+ >
1812
+ ${valueCount}
1813
+ </div>`
1814
+ : ''}
1815
+ </div>`}
1816
+ </div>
1817
+ <div
1818
+ class="accordion-content ${isCollapsed ? 'collapsed' : '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