@nyaruka/temba-components 0.130.3 → 0.130.5

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 (99) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/demo/sortable-rules-demo.html +155 -0
  3. package/dist/temba-components.js +133 -143
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/events.js.map +1 -1
  6. package/out-tsc/src/flow/CanvasNode.js +13 -7
  7. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  8. package/out-tsc/src/flow/actions/send_msg.js +1 -0
  9. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  10. package/out-tsc/src/flow/nodes/split_by_groups.js +149 -1
  11. package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
  12. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +1 -0
  13. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  14. package/out-tsc/src/flow/nodes/wait_for_response.js +81 -75
  15. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  16. package/out-tsc/src/form/ArrayEditor.js +106 -28
  17. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  18. package/out-tsc/src/form/select/Select.js +21 -25
  19. package/out-tsc/src/form/select/Select.js.map +1 -1
  20. package/out-tsc/src/list/SortableList.js +214 -140
  21. package/out-tsc/src/list/SortableList.js.map +1 -1
  22. package/out-tsc/src/live/ContactChat.js +18 -13
  23. package/out-tsc/src/live/ContactChat.js.map +1 -1
  24. package/out-tsc/test/nodes/split_by_groups.test.js +130 -0
  25. package/out-tsc/test/nodes/split_by_groups.test.js.map +1 -0
  26. package/out-tsc/test/nodes/wait_for_response.test.js +149 -0
  27. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
  28. package/out-tsc/test/temba-field-config.test.js +56 -0
  29. package/out-tsc/test/temba-field-config.test.js.map +1 -1
  30. package/package.json +1 -1
  31. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  32. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  33. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  34. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  35. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  36. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  37. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  38. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  39. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  40. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  41. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  42. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  43. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  44. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  45. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  46. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  47. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  48. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  49. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  50. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  51. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  52. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  53. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  54. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  55. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  56. package/screenshots/truth/contacts/chat-failure.png +0 -0
  57. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  58. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  59. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  60. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  61. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  62. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  63. package/screenshots/truth/editor/wait.png +0 -0
  64. package/screenshots/truth/field-renderer/select-with-label.png +0 -0
  65. package/screenshots/truth/list/fields-dragging.png +0 -0
  66. package/screenshots/truth/list/sortable-dragging.png +0 -0
  67. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  68. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  69. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  70. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  71. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  72. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  73. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  74. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  75. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  76. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  77. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  78. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  79. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  80. package/screenshots/truth/select/search-enabled.png +0 -0
  81. package/screenshots/truth/select/search-selected-focus.png +0 -0
  82. package/screenshots/truth/select/search-selected.png +0 -0
  83. package/screenshots/truth/templates/default.png +0 -0
  84. package/screenshots/truth/templates/unapproved.png +0 -0
  85. package/src/events.ts +6 -6
  86. package/src/flow/CanvasNode.ts +15 -13
  87. package/src/flow/actions/send_msg.ts +1 -0
  88. package/src/flow/nodes/split_by_groups.ts +190 -1
  89. package/src/flow/nodes/split_by_llm_categorize.ts +1 -0
  90. package/src/flow/nodes/wait_for_response.ts +98 -74
  91. package/src/form/ArrayEditor.ts +112 -28
  92. package/src/form/select/Select.ts +24 -25
  93. package/src/list/SortableList.ts +250 -149
  94. package/src/live/ContactChat.ts +20 -13
  95. package/test/nodes/split_by_groups.test.ts +165 -0
  96. package/test/nodes/wait_for_response.test.ts +182 -0
  97. package/test/temba-field-config.test.ts +69 -0
  98. package/test-assets/contacts/history.json +37 -35
  99. package/screenshots/truth/contacts/chat-for-active-contact.png +0 -0
@@ -3,11 +3,14 @@ import { html, css } from 'lit';
3
3
  import { customElement, property } from 'lit/decorators.js';
4
4
  import { BaseListEditor } from './BaseListEditor';
5
5
  import { FieldRenderer } from './FieldRenderer';
6
+ import '../list/SortableList';
7
+ import { Icon } from '../Icons';
6
8
  let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
7
9
  constructor() {
8
10
  super();
9
11
  this.itemConfig = {};
10
12
  this.itemLabel = 'Item';
13
+ this.sortable = false;
11
14
  this.maintainEmptyItem = true; // Enable by default for better UX
12
15
  this._items = [];
13
16
  }
@@ -58,6 +61,70 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
58
61
  }
59
62
  this.updateValue(updatedItems);
60
63
  }
64
+ handleOrderChanged(event) {
65
+ const detail = event.detail;
66
+ // Handle swap-based logic from SortableList
67
+ if (detail.swap && Array.isArray(detail.swap) && detail.swap.length === 2) {
68
+ const [fromIdx, toIdx] = detail.swap;
69
+ // Only reorder if the indexes are different and valid
70
+ if (fromIdx !== toIdx &&
71
+ fromIdx >= 0 &&
72
+ toIdx >= 0 &&
73
+ fromIdx < this._items.length &&
74
+ toIdx < this._items.length) {
75
+ const updatedItems = [...this._items];
76
+ // Move the item using splice operations
77
+ const movedItem = updatedItems.splice(fromIdx, 1)[0];
78
+ updatedItems.splice(toIdx, 0, movedItem);
79
+ this.updateValue(updatedItems);
80
+ }
81
+ }
82
+ }
83
+ renderWidget() {
84
+ const items = this.displayItems;
85
+ const itemsContent = items.map((item, index) => {
86
+ const renderedItem = this.renderItem(item, index);
87
+ if (this.sortable && !this.isEmptyItem(item)) {
88
+ // Wrap non-empty items with sortable class and unique ID for drag-and-drop
89
+ return html `
90
+ <div class="sortable" id="array-item-${index}">${renderedItem}</div>
91
+ `;
92
+ }
93
+ else {
94
+ // Non-sortable items or empty items don't get the sortable wrapper
95
+ return renderedItem;
96
+ }
97
+ });
98
+ if (this.sortable) {
99
+ return html `
100
+ <div class=${this.getContainerClass()}>
101
+ <temba-sortable-list
102
+ dragHandle="drag-handle"
103
+ gap="0.4em"
104
+ @temba-order-changed=${this.handleOrderChanged}
105
+ style="display: grid; grid-template-columns: 1fr; gap: 8px;"
106
+ >
107
+ ${itemsContent}
108
+ </temba-sortable-list>
109
+ ${this.shouldShowAddButton() ? this.renderAddButton() : ''}
110
+ </div>
111
+ `;
112
+ }
113
+ else {
114
+ // Non-sortable rendering (original behavior)
115
+ return html `
116
+ <div class=${this.getContainerClass()}>
117
+ <div
118
+ class="list-items"
119
+ style="display: grid; grid-template-columns: 1fr; gap: 8px;"
120
+ >
121
+ ${itemsContent}
122
+ </div>
123
+ ${this.shouldShowAddButton() ? this.renderAddButton() : ''}
124
+ </div>
125
+ `;
126
+ }
127
+ }
61
128
  computeFieldValue(itemIndex, fieldName, config) {
62
129
  const item = this._items[itemIndex] || {};
63
130
  const currentValue = item[fieldName];
@@ -138,11 +205,9 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
138
205
  }
139
206
  fieldElements.push(html `
140
207
  <div
141
- class="field ${config.width ||
142
- config.maxWidth ||
143
- config.type === 'select'
144
- ? 'field-fixed'
145
- : 'field-flex'}"
208
+ style="${config.width || config.maxWidth || config.type === 'select'
209
+ ? 'flex:none'
210
+ : 'flex:1'}"
146
211
  >
147
212
  ${this.renderArrayField(index, fieldName, config)}
148
213
  </div>
@@ -156,11 +221,31 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
156
221
  }
157
222
  return html `
158
223
  <div class="array-item">
159
- <div class="item-fields">
224
+ <div
225
+ class="item-fields ${canRemove ? '' : 'removable'}"
226
+ style="display: flex; gap: 12px; align-items: center"
227
+ >
228
+ ${this.sortable
229
+ ? html `<temba-icon
230
+ name=${Icon.sort}
231
+ style="margin-right: -6px;"
232
+ class="drag-handle"
233
+ ></temba-icon>`
234
+ : null}
160
235
  ${fieldElements}
161
236
  <button
162
237
  @click=${canRemove ? () => this.removeItem(index) : undefined}
163
- class="remove-btn ${canRemove ? '' : 'invisible'}"
238
+ class="remove-btn"
239
+ style="
240
+ padding: 4px;
241
+ border: 1px solid #ccc;
242
+ border-radius: 4px;
243
+ background: white;
244
+ cursor: pointer;
245
+ background: #fefefe;
246
+ color: #999;
247
+ font-size: 14px;
248
+ "
164
249
  ?disabled=${!canRemove}
165
250
  >
166
251
  <temba-icon name="x"></temba-icon>
@@ -176,12 +261,6 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
176
261
  return css `
177
262
  ${super.styles}
178
263
 
179
- .array-editor {
180
- }
181
-
182
- .array-item {
183
- }
184
-
185
264
  .item-header {
186
265
  display: flex;
187
266
  justify-content: space-between;
@@ -193,24 +272,10 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
193
272
  color: #333;
194
273
  }
195
274
 
196
- .item-fields {
197
- display: flex;
198
- gap: 12px;
199
- align-items: center;
200
- }
201
-
202
275
  .field {
203
276
  /* Base field styles */
204
277
  }
205
278
 
206
- .field-flex {
207
- flex: 1; /* Grow to fill remaining space */
208
- }
209
-
210
- .field-fixed {
211
- flex: none; /* Don't grow, use content/maxWidth size */
212
- }
213
-
214
279
  .spacer {
215
280
  /* Empty spacer to maintain layout alignment */
216
281
  }
@@ -235,10 +300,20 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
235
300
  color: #999;
236
301
  }
237
302
 
238
- .remove-btn.invisible {
303
+ .removable .remove-btn {
304
+ visibility: hidden;
305
+ cursor: default;
306
+ }
307
+
308
+ .removable .drag-handle {
239
309
  visibility: hidden;
240
310
  cursor: default;
241
311
  }
312
+
313
+ .drag-handle {
314
+ cursor: grab;
315
+ color: #ccc;
316
+ }
242
317
  `;
243
318
  }
244
319
  };
@@ -254,6 +329,9 @@ __decorate([
254
329
  __decorate([
255
330
  property({ type: Function })
256
331
  ], TembaArrayEditor.prototype, "isEmptyItemFn", void 0);
332
+ __decorate([
333
+ property({ type: Boolean })
334
+ ], TembaArrayEditor.prototype, "sortable", void 0);
257
335
  __decorate([
258
336
  property({ type: Boolean })
259
337
  ], TembaArrayEditor.prototype, "maintainEmptyItem", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"ArrayEditor.js","sourceRoot":"","sources":["../../../src/form/ArrayEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, TemplateResult } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { FieldConfig } from '../flow/types';\nimport { BaseListEditor, ListItem } from './BaseListEditor';\nimport { FieldRenderer } from './FieldRenderer';\n\n@customElement('temba-array-editor')\nexport class TembaArrayEditor extends BaseListEditor<ListItem> {\n @property({ type: Object })\n itemConfig: Record<string, FieldConfig> = {};\n\n @property({ type: String })\n itemLabel = 'Item';\n\n @property({ type: Function })\n onItemChange?: (\n itemIndex: number,\n field: string,\n value: any,\n allItems: any[]\n ) => any[];\n\n @property({ type: Function })\n isEmptyItemFn?: (item: any) => boolean;\n\n @property({ type: Boolean })\n maintainEmptyItem = true; // Enable by default for better UX\n\n constructor() {\n super();\n this._items = [];\n }\n\n // External API\n @property({ type: Array })\n get value(): any[] {\n return [...this._items];\n }\n\n set value(newValue: any[]) {\n this._items = newValue || [];\n this.requestUpdate();\n }\n\n // Implement abstract methods\n isEmptyItem(item: ListItem): boolean {\n // Use configurable function if provided\n if (this.isEmptyItemFn) {\n return this.isEmptyItemFn(item);\n }\n\n // Default behavior: check if all values are empty\n const values = Object.values(item);\n if (values.length === 0) {\n return true;\n }\n\n return values.every(\n (value) => value === undefined || value === null || value === ''\n );\n }\n\n // Override cleanItems to be more permissive for form data\n protected cleanItems(items: ListItem[]): any {\n // For runtime attachments, keep items that have at least one non-empty field\n return items.filter((item) => {\n const values = Object.values(item);\n return (\n values.length > 0 &&\n values.some(\n (value) => value !== undefined && value !== null && value !== ''\n )\n );\n });\n }\n\n createEmptyItem(): ListItem {\n return {};\n }\n\n protected handleFieldChange(\n itemIndex: number,\n fieldName: string,\n newValue: any\n ) {\n let updatedItems: any[];\n\n if (this.onItemChange) {\n updatedItems = this.onItemChange(\n itemIndex,\n fieldName,\n newValue,\n this._items\n );\n } else {\n updatedItems = [...this._items];\n updatedItems[itemIndex] = {\n ...updatedItems[itemIndex],\n [fieldName]: newValue\n };\n }\n\n this.updateValue(updatedItems);\n }\n\n private computeFieldValue(\n itemIndex: number,\n fieldName: string,\n config: FieldConfig\n ): any {\n const item = this._items[itemIndex] || {};\n const currentValue = item[fieldName];\n\n if (config.computeValue) {\n return config.computeValue(item, currentValue);\n }\n\n // For select fields, ensure we return the right type\n /*if (config.type === 'select') {\n console.log('computeFieldValue select', currentValue, config);\n const selectConfig = config as SelectFieldConfig;\n if (currentValue === undefined || currentValue === null) {\n return selectConfig.multi ? [] : '';\n }\n }*/\n\n return currentValue;\n }\n\n private renderArrayField(\n itemIndex: number,\n fieldName: string,\n config: FieldConfig\n ): TemplateResult {\n const computedValue = this.computeFieldValue(itemIndex, fieldName, config);\n\n // Extract flavor from select config if available\n const flavor =\n config.type === 'select' ? (config as any).flavor || 'small' : 'small';\n\n // Build container style with width/maxWidth if specified\n let containerStyle = '';\n if (config.width) {\n containerStyle = `width: ${config.width};`;\n } else if (config.maxWidth) {\n containerStyle = `max-width: ${config.maxWidth};`;\n }\n\n // Use FieldRenderer for consistent field rendering\n const fieldContent = FieldRenderer.renderField(\n fieldName,\n config,\n computedValue,\n {\n showLabel: false, // ArrayEditor doesn't show labels for individual fields\n flavor: flavor,\n extraClasses: 'form-control',\n onChange: (e: Event) => {\n let value: any;\n const target = e.target as any;\n\n // Handle different field types and their change events\n if (config.type === 'select') {\n // Use consistent temba-select value normalization\n value = target.values;\n } else {\n // For other field types, use the target value directly\n value = target.value;\n }\n\n this.handleFieldChange(itemIndex, fieldName, value);\n }\n }\n );\n\n // Wrap in container with style if maxWidth is specified\n if (containerStyle) {\n return html`<div style=\"${containerStyle}\">${fieldContent}</div>`;\n }\n\n return fieldContent;\n }\n\n renderItem(item: ListItem, index: number): TemplateResult {\n const canRemove = this.canRemoveItem(index);\n\n // Render fields and track if any value fields are visible\n const fieldElements: TemplateResult[] = [];\n let hasVisibleValueField = false;\n\n Object.entries(this.itemConfig).forEach(([fieldName, config]) => {\n // Check visibility condition\n let isVisible = true;\n if (config.conditions?.visible) {\n try {\n const currentItem = this._items[index] || {};\n isVisible = config.conditions.visible(currentItem);\n } catch (error) {\n console.error(`Error checking visibility for ${fieldName}:`, error);\n }\n }\n\n if (isVisible) {\n // Check if this is a value field (text input without fixed sizing)\n const isValueField =\n !config.width && !config.maxWidth && config.type === 'text';\n if (isValueField) {\n hasVisibleValueField = true;\n }\n\n fieldElements.push(html`\n <div\n class=\"field ${config.width ||\n config.maxWidth ||\n config.type === 'select'\n ? 'field-fixed'\n : 'field-flex'}\"\n >\n ${this.renderArrayField(index, fieldName, config)}\n </div>\n `);\n }\n });\n\n // If no value fields are visible, add a spacer to maintain alignment\n if (!hasVisibleValueField) {\n // Insert spacer after operator (first field) and before category (last field)\n fieldElements.splice(\n -1,\n 0,\n html`<div class=\"field field-flex spacer\"></div>`\n );\n }\n\n return html`\n <div class=\"array-item\">\n <div class=\"item-fields\">\n ${fieldElements}\n <button\n @click=${canRemove ? () => this.removeItem(index) : undefined}\n class=\"remove-btn ${canRemove ? '' : 'invisible'}\"\n ?disabled=${!canRemove}\n >\n <temba-icon name=\"x\"></temba-icon>\n </button>\n </div>\n </div>\n `;\n }\n\n protected getContainerClass(): string {\n return 'array-editor';\n }\n\n static get styles() {\n return css`\n ${super.styles}\n\n .array-editor {\n }\n\n .array-item {\n }\n\n .item-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .item-title {\n font-weight: 600;\n color: #333;\n }\n\n .item-fields {\n display: flex;\n gap: 12px;\n align-items: center;\n }\n\n .field {\n /* Base field styles */\n }\n\n .field-flex {\n flex: 1; /* Grow to fill remaining space */\n }\n\n .field-fixed {\n flex: none; /* Don't grow, use content/maxWidth size */\n }\n\n .spacer {\n /* Empty spacer to maintain layout alignment */\n }\n\n .add-btn,\n .remove-btn {\n padding: 4px;\n border: 1px solid #ccc;\n border-radius: 4px;\n background: white;\n cursor: pointer;\n font-size: 14px;\n }\n\n .add-btn:hover,\n .remove-btn:hover {\n background: #f8f8f8;\n }\n\n .remove-btn {\n background: #fefefe;\n color: #999;\n }\n\n .remove-btn.invisible {\n visibility: hidden;\n cursor: default;\n }\n `;\n }\n}\n"]}
1
+ {"version":3,"file":"ArrayEditor.js","sourceRoot":"","sources":["../../../src/form/ArrayEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, TemplateResult } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { FieldConfig } from '../flow/types';\nimport { BaseListEditor, ListItem } from './BaseListEditor';\nimport { FieldRenderer } from './FieldRenderer';\nimport '../list/SortableList';\nimport { Icon } from '../Icons';\n\n@customElement('temba-array-editor')\nexport class TembaArrayEditor extends BaseListEditor<ListItem> {\n @property({ type: Object })\n itemConfig: Record<string, FieldConfig> = {};\n\n @property({ type: String })\n itemLabel = 'Item';\n\n @property({ type: Function })\n onItemChange?: (\n itemIndex: number,\n field: string,\n value: any,\n allItems: any[]\n ) => any[];\n\n @property({ type: Function })\n isEmptyItemFn?: (item: any) => boolean;\n\n @property({ type: Boolean })\n sortable = false;\n\n @property({ type: Boolean })\n maintainEmptyItem = true; // Enable by default for better UX\n\n constructor() {\n super();\n this._items = [];\n }\n\n // External API\n @property({ type: Array })\n get value(): any[] {\n return [...this._items];\n }\n\n set value(newValue: any[]) {\n this._items = newValue || [];\n this.requestUpdate();\n }\n\n // Implement abstract methods\n isEmptyItem(item: ListItem): boolean {\n // Use configurable function if provided\n if (this.isEmptyItemFn) {\n return this.isEmptyItemFn(item);\n }\n\n // Default behavior: check if all values are empty\n const values = Object.values(item);\n if (values.length === 0) {\n return true;\n }\n\n return values.every(\n (value) => value === undefined || value === null || value === ''\n );\n }\n\n // Override cleanItems to be more permissive for form data\n protected cleanItems(items: ListItem[]): any {\n // For runtime attachments, keep items that have at least one non-empty field\n return items.filter((item) => {\n const values = Object.values(item);\n return (\n values.length > 0 &&\n values.some(\n (value) => value !== undefined && value !== null && value !== ''\n )\n );\n });\n }\n\n createEmptyItem(): ListItem {\n return {};\n }\n\n protected handleFieldChange(\n itemIndex: number,\n fieldName: string,\n newValue: any\n ) {\n let updatedItems: any[];\n\n if (this.onItemChange) {\n updatedItems = this.onItemChange(\n itemIndex,\n fieldName,\n newValue,\n this._items\n );\n } else {\n updatedItems = [...this._items];\n updatedItems[itemIndex] = {\n ...updatedItems[itemIndex],\n [fieldName]: newValue\n };\n }\n\n this.updateValue(updatedItems);\n }\n\n private handleOrderChanged(event: CustomEvent): void {\n const detail = event.detail;\n\n // Handle swap-based logic from SortableList\n if (detail.swap && Array.isArray(detail.swap) && detail.swap.length === 2) {\n const [fromIdx, toIdx] = detail.swap;\n\n // Only reorder if the indexes are different and valid\n if (\n fromIdx !== toIdx &&\n fromIdx >= 0 &&\n toIdx >= 0 &&\n fromIdx < this._items.length &&\n toIdx < this._items.length\n ) {\n const updatedItems = [...this._items];\n // Move the item using splice operations\n const movedItem = updatedItems.splice(fromIdx, 1)[0];\n updatedItems.splice(toIdx, 0, movedItem);\n this.updateValue(updatedItems);\n }\n }\n }\n\n renderWidget(): TemplateResult {\n const items = this.displayItems;\n\n const itemsContent = items.map((item, index) => {\n const renderedItem = this.renderItem(item, index);\n\n if (this.sortable && !this.isEmptyItem(item)) {\n // Wrap non-empty items with sortable class and unique ID for drag-and-drop\n return html`\n <div class=\"sortable\" id=\"array-item-${index}\">${renderedItem}</div>\n `;\n } else {\n // Non-sortable items or empty items don't get the sortable wrapper\n return renderedItem;\n }\n });\n\n if (this.sortable) {\n return html`\n <div class=${this.getContainerClass()}>\n <temba-sortable-list\n dragHandle=\"drag-handle\"\n gap=\"0.4em\"\n @temba-order-changed=${this.handleOrderChanged}\n style=\"display: grid; grid-template-columns: 1fr; gap: 8px;\"\n >\n ${itemsContent}\n </temba-sortable-list>\n ${this.shouldShowAddButton() ? this.renderAddButton() : ''}\n </div>\n `;\n } else {\n // Non-sortable rendering (original behavior)\n return html`\n <div class=${this.getContainerClass()}>\n <div\n class=\"list-items\"\n style=\"display: grid; grid-template-columns: 1fr; gap: 8px;\"\n >\n ${itemsContent}\n </div>\n ${this.shouldShowAddButton() ? this.renderAddButton() : ''}\n </div>\n `;\n }\n }\n\n private computeFieldValue(\n itemIndex: number,\n fieldName: string,\n config: FieldConfig\n ): any {\n const item = this._items[itemIndex] || {};\n const currentValue = item[fieldName];\n\n if (config.computeValue) {\n return config.computeValue(item, currentValue);\n }\n\n // For select fields, ensure we return the right type\n /*if (config.type === 'select') {\n console.log('computeFieldValue select', currentValue, config);\n const selectConfig = config as SelectFieldConfig;\n if (currentValue === undefined || currentValue === null) {\n return selectConfig.multi ? [] : '';\n }\n }*/\n\n return currentValue;\n }\n\n private renderArrayField(\n itemIndex: number,\n fieldName: string,\n config: FieldConfig\n ): TemplateResult {\n const computedValue = this.computeFieldValue(itemIndex, fieldName, config);\n\n // Extract flavor from select config if available\n const flavor =\n config.type === 'select' ? (config as any).flavor || 'small' : 'small';\n\n // Build container style with width/maxWidth if specified\n let containerStyle = '';\n if (config.width) {\n containerStyle = `width: ${config.width};`;\n } else if (config.maxWidth) {\n containerStyle = `max-width: ${config.maxWidth};`;\n }\n\n // Use FieldRenderer for consistent field rendering\n const fieldContent = FieldRenderer.renderField(\n fieldName,\n config,\n computedValue,\n {\n showLabel: false, // ArrayEditor doesn't show labels for individual fields\n flavor: flavor,\n extraClasses: 'form-control',\n onChange: (e: Event) => {\n let value: any;\n const target = e.target as any;\n\n // Handle different field types and their change events\n if (config.type === 'select') {\n // Use consistent temba-select value normalization\n value = target.values;\n } else {\n // For other field types, use the target value directly\n value = target.value;\n }\n\n this.handleFieldChange(itemIndex, fieldName, value);\n }\n }\n );\n\n // Wrap in container with style if maxWidth is specified\n if (containerStyle) {\n return html`<div style=\"${containerStyle}\">${fieldContent}</div>`;\n }\n\n return fieldContent;\n }\n\n renderItem(item: ListItem, index: number): TemplateResult {\n const canRemove = this.canRemoveItem(index);\n\n // Render fields and track if any value fields are visible\n const fieldElements: TemplateResult[] = [];\n let hasVisibleValueField = false;\n\n Object.entries(this.itemConfig).forEach(([fieldName, config]) => {\n // Check visibility condition\n let isVisible = true;\n if (config.conditions?.visible) {\n try {\n const currentItem = this._items[index] || {};\n isVisible = config.conditions.visible(currentItem);\n } catch (error) {\n console.error(`Error checking visibility for ${fieldName}:`, error);\n }\n }\n\n if (isVisible) {\n // Check if this is a value field (text input without fixed sizing)\n const isValueField =\n !config.width && !config.maxWidth && config.type === 'text';\n if (isValueField) {\n hasVisibleValueField = true;\n }\n\n fieldElements.push(html`\n <div\n style=\"${config.width || config.maxWidth || config.type === 'select'\n ? 'flex:none'\n : 'flex:1'}\"\n >\n ${this.renderArrayField(index, fieldName, config)}\n </div>\n `);\n }\n });\n\n // If no value fields are visible, add a spacer to maintain alignment\n if (!hasVisibleValueField) {\n // Insert spacer after operator (first field) and before category (last field)\n fieldElements.splice(\n -1,\n 0,\n html`<div class=\"field field-flex spacer\"></div>`\n );\n }\n\n return html`\n <div class=\"array-item\">\n <div\n class=\"item-fields ${canRemove ? '' : 'removable'}\"\n style=\"display: flex; gap: 12px; align-items: center\"\n >\n ${this.sortable\n ? html`<temba-icon\n name=${Icon.sort}\n style=\"margin-right: -6px;\"\n class=\"drag-handle\"\n ></temba-icon>`\n : null}\n ${fieldElements}\n <button\n @click=${canRemove ? () => this.removeItem(index) : undefined}\n class=\"remove-btn\"\n style=\"\n padding: 4px;\n border: 1px solid #ccc;\n border-radius: 4px;\n background: white;\n cursor: pointer;\n background: #fefefe;\n color: #999;\n font-size: 14px;\n \"\n ?disabled=${!canRemove}\n >\n <temba-icon name=\"x\"></temba-icon>\n </button>\n </div>\n </div>\n `;\n }\n\n protected getContainerClass(): string {\n return 'array-editor';\n }\n\n static get styles() {\n return css`\n ${super.styles}\n\n .item-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .item-title {\n font-weight: 600;\n color: #333;\n }\n\n .field {\n /* Base field styles */\n }\n\n .spacer {\n /* Empty spacer to maintain layout alignment */\n }\n\n .add-btn,\n .remove-btn {\n padding: 4px;\n border: 1px solid #ccc;\n border-radius: 4px;\n background: white;\n cursor: pointer;\n font-size: 14px;\n }\n\n .add-btn:hover,\n .remove-btn:hover {\n background: #f8f8f8;\n }\n\n .remove-btn {\n background: #fefefe;\n color: #999;\n }\n\n .removable .remove-btn {\n visibility: hidden;\n cursor: default;\n }\n\n .removable .drag-handle {\n visibility: hidden;\n cursor: default;\n }\n\n .drag-handle {\n cursor: grab;\n color: #ccc;\n }\n `;\n }\n}\n"]}
@@ -223,13 +223,15 @@ export class Select extends FieldElement {
223
223
 
224
224
  .input-wrapper:focus-within {
225
225
  min-width: 1px;
226
+ display: flex;
226
227
  }
227
228
 
228
229
  .input-wrapper {
229
230
  min-width: 1px;
230
231
  margin-left: 6px;
231
232
  margin-right: -6px;
232
- display: flex;
233
+ display: none;
234
+ pointer-events: none;
233
235
  }
234
236
 
235
237
  .multi .input-wrapper {
@@ -288,6 +290,7 @@ export class Select extends FieldElement {
288
290
  display: none;
289
291
  line-height: var(--temba-select-selected-line-height);
290
292
  margin-left: 6px;
293
+ pointer-events: none;
291
294
  }
292
295
 
293
296
  .empty .placeholder {
@@ -347,10 +350,6 @@ export class Select extends FieldElement {
347
350
  pointer-events: none;
348
351
  padding: 0px;
349
352
  }
350
-
351
- .ghost .remove-item {
352
- display: none !important;
353
- }
354
353
  `;
355
354
  }
356
355
  // Override the setter to ensure values is always an array
@@ -1328,10 +1327,13 @@ export class Select extends FieldElement {
1328
1327
  : {};
1329
1328
  const input = this.searchable
1330
1329
  ? html `
1331
- <div class="input-wrapper">
1330
+ <div
1331
+ class="input-wrapper"
1332
+ style="${this.focused ? 'display:flex;' : ''}"
1333
+ >
1332
1334
  <input
1333
1335
  class="searchbox"
1334
- style=${this.inputStyle ? styleMap(this.inputStyle) : ''}
1336
+ style="${this.inputStyle ? styleMap(this.inputStyle) : ''};"
1335
1337
  @input=${this.handleInput}
1336
1338
  @keydown=${this.handleKeyDown}
1337
1339
  @click=${this.handleClick}
@@ -1348,14 +1350,10 @@ export class Select extends FieldElement {
1348
1350
  <temba-sortable-list
1349
1351
  horizontal
1350
1352
  @temba-order-changed=${this.handleOrderChanged}
1351
- .prepareGhost=${(item) => {
1352
- item.style.transform = 'scale(1)';
1353
- item.querySelector('.remove-item').style.display = 'none';
1354
- }}
1355
1353
  >
1356
1354
  ${this.values.map((selected, index) => html `
1357
1355
  <div
1358
- class="selected-item sortable ${index === this.selectedIndex
1356
+ class="sortable selected-item ${index === this.selectedIndex
1359
1357
  ? 'focused'
1360
1358
  : ''} ${this.draggingId === `selected-${index}`
1361
1359
  ? 'dragging'
@@ -1412,7 +1410,7 @@ export class Select extends FieldElement {
1412
1410
  ${this.renderSelectedItem(selected)}
1413
1411
  </div>
1414
1412
  `)}
1415
- ${this.searchable && this.focused ? input : null}
1413
+ ${input}
1416
1414
  </temba-sortable-list>
1417
1415
  `
1418
1416
  : html `${this.values.map((selected, index) => html `
@@ -1435,15 +1433,9 @@ export class Select extends FieldElement {
1435
1433
  ? html `
1436
1434
  <div
1437
1435
  class="remove-item"
1438
- style="
1439
- cursor: pointer;
1440
- display: inline-block;
1441
- padding: 3px 6px;
1442
- border-right: 1px solid rgba(100,100,100,0.2);
1443
- margin: 0;
1444
- background: rgba(100,100,100,0.05);
1445
- margin-top:1px;
1446
- "
1436
+ style="cursor: pointer; display: inline-block; padding: 3px 6px;
1437
+ border-right: 1px solid rgba(100,100,100,0.2);
1438
+ margin: 0; background: rgba(100,100,100,0.05); margin-top:1px;"
1447
1439
  @click=${(evt) => {
1448
1440
  evt.preventDefault();
1449
1441
  evt.stopPropagation();
@@ -1473,9 +1465,9 @@ export class Select extends FieldElement {
1473
1465
  class="select-container ${classes}"
1474
1466
  @click=${this.handleContainerClick}
1475
1467
  >
1476
- <div class="left-side" >
1468
+ <div class="left-side">
1477
1469
  <slot name="prefix"></slot>
1478
- <div class="selected" >
1470
+ <div class="selected">
1479
1471
  ${this.resolving
1480
1472
  ? html `<temba-loading
1481
1473
  style="margin-left:1em"
@@ -1488,7 +1480,11 @@ export class Select extends FieldElement {
1488
1480
 
1489
1481
  ${clear}
1490
1482
 
1491
- <slot name="right"></slot>
1483
+ <slot name="right">${this.fetching
1484
+ ? html `<temba-loading
1485
+ style="position:absolute;background:rgba(255,255,255,0.7);padding:2px;border-radius:var(--curvature);right:25px"
1486
+ ></temba-loading>`
1487
+ : null}</slot>
1492
1488
  ${!this.tags && !this.emails
1493
1489
  ? html `<div
1494
1490
  class="right-side arrow"