@nyaruka/temba-components 0.130.4 → 0.131.0

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 (104) hide show
  1. package/CHANGELOG.md +10 -13
  2. package/demo/sortable-rules-demo.html +155 -0
  3. package/dist/temba-components.js +150 -159
  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/split_by_random.js +1 -0
  15. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
  16. package/out-tsc/src/flow/nodes/wait_for_response.js +332 -137
  17. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  18. package/out-tsc/src/form/ArrayEditor.js +301 -30
  19. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  20. package/out-tsc/src/form/select/Omnibox.js +4 -0
  21. package/out-tsc/src/form/select/Omnibox.js.map +1 -1
  22. package/out-tsc/src/form/select/Select.js +21 -25
  23. package/out-tsc/src/form/select/Select.js.map +1 -1
  24. package/out-tsc/src/list/SortableList.js +214 -140
  25. package/out-tsc/src/list/SortableList.js.map +1 -1
  26. package/out-tsc/src/live/ContactChat.js +9 -5
  27. package/out-tsc/src/live/ContactChat.js.map +1 -1
  28. package/out-tsc/test/nodes/split_by_groups.test.js +130 -0
  29. package/out-tsc/test/nodes/split_by_groups.test.js.map +1 -0
  30. package/out-tsc/test/nodes/wait_for_response.test.js +522 -8
  31. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
  32. package/out-tsc/test/temba-field-config.test.js +56 -0
  33. package/out-tsc/test/temba-field-config.test.js.map +1 -1
  34. package/package.json +1 -1
  35. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  36. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  37. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  38. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  39. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  40. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  41. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  42. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  43. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  44. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  45. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  46. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  47. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  48. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  49. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  50. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  51. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  52. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  53. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  54. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  55. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  56. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  57. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  58. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  59. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  60. package/screenshots/truth/editor/wait.png +0 -0
  61. package/screenshots/truth/field-renderer/select-with-label.png +0 -0
  62. package/screenshots/truth/list/fields-dragging.png +0 -0
  63. package/screenshots/truth/list/sortable-dragging.png +0 -0
  64. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  65. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  66. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  67. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  68. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  69. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  70. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  71. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  72. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  73. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  74. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  75. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  76. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  77. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  78. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  79. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  80. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  81. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  82. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  83. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  84. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  85. package/screenshots/truth/select/search-enabled.png +0 -0
  86. package/screenshots/truth/select/search-selected-focus.png +0 -0
  87. package/screenshots/truth/select/search-selected.png +0 -0
  88. package/screenshots/truth/templates/default.png +0 -0
  89. package/screenshots/truth/templates/unapproved.png +0 -0
  90. package/src/events.ts +6 -6
  91. package/src/flow/CanvasNode.ts +15 -13
  92. package/src/flow/actions/send_msg.ts +1 -0
  93. package/src/flow/nodes/split_by_groups.ts +190 -1
  94. package/src/flow/nodes/split_by_llm_categorize.ts +1 -0
  95. package/src/flow/nodes/split_by_random.ts +1 -0
  96. package/src/flow/nodes/wait_for_response.ts +424 -145
  97. package/src/form/ArrayEditor.ts +372 -30
  98. package/src/form/select/Omnibox.ts +3 -0
  99. package/src/form/select/Select.ts +24 -25
  100. package/src/list/SortableList.ts +250 -149
  101. package/src/live/ContactChat.ts +11 -5
  102. package/test/nodes/split_by_groups.test.ts +165 -0
  103. package/test/nodes/wait_for_response.test.ts +608 -8
  104. package/test/temba-field-config.test.ts +69 -0
@@ -3,12 +3,17 @@ 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
15
+ // Focus preservation properties
16
+ this.focusInfo = null;
12
17
  this._items = [];
13
18
  }
14
19
  // External API
@@ -41,6 +46,181 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
41
46
  values.some((value) => value !== undefined && value !== null && value !== ''));
42
47
  });
43
48
  }
49
+ // Capture focus information before update
50
+ captureFocus() {
51
+ var _a, _b, _c, _d, _e, _f, _g;
52
+ const activeElement = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement;
53
+ // Also try document.activeElement as a fallback
54
+ const globalActive = document.activeElement;
55
+ let targetElement = activeElement || globalActive;
56
+ // If active element is within this component's shadow root, use it
57
+ if (globalActive && ((_b = this.shadowRoot) === null || _b === void 0 ? void 0 : _b.contains(globalActive))) {
58
+ targetElement = globalActive;
59
+ }
60
+ if (!targetElement) {
61
+ this.focusInfo = null;
62
+ return;
63
+ }
64
+ // Find the array item container by traversing up the DOM
65
+ let currentElement = targetElement;
66
+ let arrayItemElement = null;
67
+ // Traverse up through shadow DOM boundaries
68
+ while (currentElement) {
69
+ if ((_c = currentElement.classList) === null || _c === void 0 ? void 0 : _c.contains('array-item')) {
70
+ arrayItemElement = currentElement;
71
+ break;
72
+ }
73
+ // Move up to parent, or cross shadow boundaries
74
+ if (currentElement.parentElement) {
75
+ currentElement = currentElement.parentElement;
76
+ }
77
+ else if (currentElement.parentNode &&
78
+ currentElement.parentNode.host) {
79
+ // Cross shadow boundary
80
+ currentElement = currentElement.parentNode.host;
81
+ }
82
+ else {
83
+ break;
84
+ }
85
+ }
86
+ if (!arrayItemElement) {
87
+ this.focusInfo = null;
88
+ return;
89
+ }
90
+ // Find the item index by looking at the item ID
91
+ const itemIdMatch = (_d = arrayItemElement.id) === null || _d === void 0 ? void 0 : _d.match(/array-item-(\d+)/);
92
+ if (!itemIdMatch) {
93
+ this.focusInfo = null;
94
+ return;
95
+ }
96
+ const itemIndex = parseInt(itemIdMatch[1], 10);
97
+ // Determine the field name by examining the input element and its containers
98
+ let fieldName = '';
99
+ // First, check if it's a temba component with a name attribute
100
+ if ((_e = targetElement.tagName) === null || _e === void 0 ? void 0 : _e.toLowerCase().startsWith('temba-')) {
101
+ fieldName =
102
+ targetElement.name || targetElement.getAttribute('name') || '';
103
+ }
104
+ // If not found, check regular HTML elements
105
+ if (!fieldName &&
106
+ targetElement.hasAttribute &&
107
+ targetElement.hasAttribute('name')) {
108
+ fieldName = targetElement.getAttribute('name') || '';
109
+ }
110
+ // If still not found, look for data-field-name in parent containers
111
+ if (!fieldName) {
112
+ let searchElement = targetElement;
113
+ while (searchElement && searchElement !== arrayItemElement) {
114
+ if (searchElement.hasAttribute &&
115
+ searchElement.hasAttribute('data-field-name')) {
116
+ fieldName = searchElement.getAttribute('data-field-name') || '';
117
+ break;
118
+ }
119
+ searchElement = searchElement.parentElement;
120
+ }
121
+ }
122
+ if (!fieldName) {
123
+ this.focusInfo = null;
124
+ return;
125
+ }
126
+ // Capture selection for text inputs (try the actual input element inside temba components)
127
+ let inputForSelection = targetElement;
128
+ if ((_f = targetElement.tagName) === null || _f === void 0 ? void 0 : _f.toLowerCase().startsWith('temba-')) {
129
+ // Look for the actual input element inside the temba component
130
+ const innerInput = ((_g = targetElement.shadowRoot) === null || _g === void 0 ? void 0 : _g.querySelector('input, textarea')) ||
131
+ targetElement.querySelector('input, textarea');
132
+ if (innerInput) {
133
+ inputForSelection = innerInput;
134
+ }
135
+ }
136
+ const selectionStart = inputForSelection.selectionStart;
137
+ const selectionEnd = inputForSelection.selectionEnd;
138
+ this.focusInfo = {
139
+ itemIndex,
140
+ fieldName,
141
+ selectionStart,
142
+ selectionEnd
143
+ };
144
+ }
145
+ // Restore focus after update
146
+ restoreFocus() {
147
+ var _a, _b;
148
+ if (!this.focusInfo) {
149
+ return;
150
+ }
151
+ const { itemIndex, fieldName, selectionStart, selectionEnd } = this.focusInfo;
152
+ // Find the target element by array item index
153
+ const arrayItemId = `array-item-${itemIndex}`;
154
+ const arrayItemElement = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.getElementById(arrayItemId);
155
+ if (!arrayItemElement) {
156
+ // If the exact item doesn't exist (e.g., due to reordering), try to find by field name
157
+ const allItems = (_b = this.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelectorAll('.array-item');
158
+ if (allItems && allItems.length > itemIndex) {
159
+ const fallbackItem = allItems[itemIndex];
160
+ if (fallbackItem) {
161
+ this.attemptFocusRestore(fallbackItem, fieldName, selectionStart, selectionEnd);
162
+ }
163
+ }
164
+ this.focusInfo = null;
165
+ return;
166
+ }
167
+ this.attemptFocusRestore(arrayItemElement, fieldName, selectionStart, selectionEnd);
168
+ this.focusInfo = null;
169
+ }
170
+ attemptFocusRestore(container, fieldName, selectionStart, selectionEnd) {
171
+ // Look for the field container first
172
+ const fieldContainer = container.querySelector(`[data-field-name="${fieldName}"]`);
173
+ let targetElement = null;
174
+ if (fieldContainer) {
175
+ // Look for temba components or input elements within the field container
176
+ targetElement = fieldContainer.querySelector('temba-textinput, temba-completion, input, textarea');
177
+ }
178
+ // Fallback: search entire container
179
+ if (!targetElement) {
180
+ const selectors = [
181
+ `temba-textinput[name="${fieldName}"]`,
182
+ `temba-completion[name="${fieldName}"]`,
183
+ `input[name="${fieldName}"]`,
184
+ `textarea[name="${fieldName}"]`,
185
+ `[name="${fieldName}"]`
186
+ ];
187
+ for (const selector of selectors) {
188
+ targetElement = container.querySelector(selector);
189
+ if (targetElement)
190
+ break;
191
+ }
192
+ }
193
+ if (targetElement) {
194
+ // Use multiple animation frames to ensure DOM is fully settled
195
+ requestAnimationFrame(() => {
196
+ requestAnimationFrame(() => {
197
+ var _a, _b;
198
+ try {
199
+ targetElement.focus();
200
+ // Restore selection if it's a text input
201
+ if (selectionStart !== undefined && selectionEnd !== undefined) {
202
+ // For temba components, we need to focus the inner input
203
+ let inputForSelection = targetElement;
204
+ if ((_a = targetElement.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase().startsWith('temba-')) {
205
+ const innerInput = ((_b = targetElement.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('input, textarea')) ||
206
+ targetElement.querySelector('input, textarea');
207
+ if (innerInput && 'setSelectionRange' in innerInput) {
208
+ inputForSelection = innerInput;
209
+ }
210
+ }
211
+ if ('setSelectionRange' in inputForSelection) {
212
+ inputForSelection.setSelectionRange(selectionStart, selectionEnd);
213
+ }
214
+ }
215
+ }
216
+ catch (error) {
217
+ // Ignore focus errors - element might not be focusable
218
+ // Focus restoration failed, silently continue
219
+ }
220
+ });
221
+ });
222
+ }
223
+ }
44
224
  createEmptyItem() {
45
225
  return {};
46
226
  }
@@ -58,6 +238,85 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
58
238
  }
59
239
  this.updateValue(updatedItems);
60
240
  }
241
+ // Override Lit's update lifecycle methods for focus preservation
242
+ willUpdate(changedProperties) {
243
+ super.willUpdate(changedProperties);
244
+ // Capture focus before update if items are changing
245
+ if (changedProperties.has('_items') || changedProperties.has('value')) {
246
+ this.captureFocus();
247
+ }
248
+ }
249
+ updated(changedProperties) {
250
+ super.updated(changedProperties);
251
+ // Restore focus after update if items changed
252
+ if (changedProperties.has('_items') || changedProperties.has('value')) {
253
+ this.restoreFocus();
254
+ }
255
+ }
256
+ handleOrderChanged(event) {
257
+ const detail = event.detail;
258
+ // Handle swap-based logic from SortableList
259
+ if (detail.swap && Array.isArray(detail.swap) && detail.swap.length === 2) {
260
+ const [fromIdx, toIdx] = detail.swap;
261
+ // Only reorder if the indexes are different and valid
262
+ if (fromIdx !== toIdx &&
263
+ fromIdx >= 0 &&
264
+ toIdx >= 0 &&
265
+ fromIdx < this._items.length &&
266
+ toIdx < this._items.length) {
267
+ const updatedItems = [...this._items];
268
+ // Move the item using splice operations
269
+ const movedItem = updatedItems.splice(fromIdx, 1)[0];
270
+ updatedItems.splice(toIdx, 0, movedItem);
271
+ this.updateValue(updatedItems);
272
+ }
273
+ }
274
+ }
275
+ renderWidget() {
276
+ const items = this.displayItems;
277
+ const itemsContent = items.map((item, index) => {
278
+ const renderedItem = this.renderItem(item, index);
279
+ if (this.sortable && !this.isEmptyItem(item)) {
280
+ // Wrap non-empty items with sortable class and unique ID for drag-and-drop
281
+ return html `
282
+ <div class="sortable" id="array-item-${index}">${renderedItem}</div>
283
+ `;
284
+ }
285
+ else {
286
+ // Non-sortable items or empty items don't get the sortable wrapper
287
+ return renderedItem;
288
+ }
289
+ });
290
+ if (this.sortable) {
291
+ return html `
292
+ <div class=${this.getContainerClass()}>
293
+ <temba-sortable-list
294
+ dragHandle="drag-handle"
295
+ gap="0.4em"
296
+ @temba-order-changed=${this.handleOrderChanged}
297
+ style="display: grid; grid-template-columns: 1fr; gap: 8px;"
298
+ >
299
+ ${itemsContent}
300
+ </temba-sortable-list>
301
+ ${this.shouldShowAddButton() ? this.renderAddButton() : ''}
302
+ </div>
303
+ `;
304
+ }
305
+ else {
306
+ // Non-sortable rendering (original behavior)
307
+ return html `
308
+ <div class=${this.getContainerClass()}>
309
+ <div
310
+ class="list-items"
311
+ style="display: grid; grid-template-columns: 1fr; gap: 8px;"
312
+ >
313
+ ${itemsContent}
314
+ </div>
315
+ ${this.shouldShowAddButton() ? this.renderAddButton() : ''}
316
+ </div>
317
+ `;
318
+ }
319
+ }
61
320
  computeFieldValue(itemIndex, fieldName, config) {
62
321
  const item = this._items[itemIndex] || {};
63
322
  const currentValue = item[fieldName];
@@ -138,11 +397,10 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
138
397
  }
139
398
  fieldElements.push(html `
140
399
  <div
141
- class="field ${config.width ||
142
- config.maxWidth ||
143
- config.type === 'select'
144
- ? 'field-fixed'
145
- : 'field-flex'}"
400
+ data-field-name="${fieldName}"
401
+ style="${config.width || config.maxWidth || config.type === 'select'
402
+ ? 'flex:none'
403
+ : 'flex:1'}"
146
404
  >
147
405
  ${this.renderArrayField(index, fieldName, config)}
148
406
  </div>
@@ -152,15 +410,35 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
152
410
  // If no value fields are visible, add a spacer to maintain alignment
153
411
  if (!hasVisibleValueField) {
154
412
  // Insert spacer after operator (first field) and before category (last field)
155
- fieldElements.splice(-1, 0, html `<div class="field field-flex spacer"></div>`);
413
+ fieldElements.splice(-1, 0, html `<div class="field field-flex spacer" style="flex-grow:1"></div>`);
156
414
  }
157
415
  return html `
158
- <div class="array-item">
159
- <div class="item-fields">
416
+ <div class="array-item" id="array-item-${index}">
417
+ <div
418
+ class="item-fields ${canRemove ? '' : 'removable'}"
419
+ style="display: flex; gap: 12px; align-items: center"
420
+ >
421
+ ${this.sortable
422
+ ? html `<temba-icon
423
+ name=${Icon.sort}
424
+ style="margin-right: -6px;"
425
+ class="drag-handle"
426
+ ></temba-icon>`
427
+ : null}
160
428
  ${fieldElements}
161
429
  <button
162
430
  @click=${canRemove ? () => this.removeItem(index) : undefined}
163
- class="remove-btn ${canRemove ? '' : 'invisible'}"
431
+ class="remove-btn"
432
+ style="
433
+ padding: 4px;
434
+ border: 1px solid #ccc;
435
+ border-radius: 4px;
436
+ background: white;
437
+ cursor: pointer;
438
+ background: #fefefe;
439
+ color: #999;
440
+ font-size: 14px;
441
+ "
164
442
  ?disabled=${!canRemove}
165
443
  >
166
444
  <temba-icon name="x"></temba-icon>
@@ -176,12 +454,6 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
176
454
  return css `
177
455
  ${super.styles}
178
456
 
179
- .array-editor {
180
- }
181
-
182
- .array-item {
183
- }
184
-
185
457
  .item-header {
186
458
  display: flex;
187
459
  justify-content: space-between;
@@ -193,24 +465,10 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
193
465
  color: #333;
194
466
  }
195
467
 
196
- .item-fields {
197
- display: flex;
198
- gap: 12px;
199
- align-items: center;
200
- }
201
-
202
468
  .field {
203
469
  /* Base field styles */
204
470
  }
205
471
 
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
472
  .spacer {
215
473
  /* Empty spacer to maintain layout alignment */
216
474
  }
@@ -235,10 +493,20 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
235
493
  color: #999;
236
494
  }
237
495
 
238
- .remove-btn.invisible {
496
+ .removable .remove-btn {
239
497
  visibility: hidden;
240
498
  cursor: default;
241
499
  }
500
+
501
+ .removable .drag-handle {
502
+ visibility: hidden;
503
+ cursor: default;
504
+ }
505
+
506
+ .drag-handle {
507
+ cursor: grab;
508
+ color: #ccc;
509
+ }
242
510
  `;
243
511
  }
244
512
  };
@@ -254,6 +522,9 @@ __decorate([
254
522
  __decorate([
255
523
  property({ type: Function })
256
524
  ], TembaArrayEditor.prototype, "isEmptyItemFn", void 0);
525
+ __decorate([
526
+ property({ type: Boolean })
527
+ ], TembaArrayEditor.prototype, "sortable", void 0);
257
528
  __decorate([
258
529
  property({ type: Boolean })
259
530
  ], 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 // Focus preservation properties\n private focusInfo: {\n itemIndex: number;\n fieldName: string;\n selectionStart?: number;\n selectionEnd?: number;\n } | null = null;\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 // Capture focus information before update\n private captureFocus(): void {\n const activeElement = this.shadowRoot?.activeElement as HTMLElement;\n\n // Also try document.activeElement as a fallback\n const globalActive = document.activeElement as HTMLElement;\n let targetElement = activeElement || globalActive;\n\n // If active element is within this component's shadow root, use it\n if (globalActive && this.shadowRoot?.contains(globalActive)) {\n targetElement = globalActive;\n }\n\n if (!targetElement) {\n this.focusInfo = null;\n return;\n }\n\n // Find the array item container by traversing up the DOM\n let currentElement = targetElement;\n let arrayItemElement: HTMLElement | null = null;\n\n // Traverse up through shadow DOM boundaries\n while (currentElement) {\n if (currentElement.classList?.contains('array-item')) {\n arrayItemElement = currentElement;\n break;\n }\n\n // Move up to parent, or cross shadow boundaries\n if (currentElement.parentElement) {\n currentElement = currentElement.parentElement;\n } else if (\n currentElement.parentNode &&\n (currentElement.parentNode as any).host\n ) {\n // Cross shadow boundary\n currentElement = (currentElement.parentNode as any).host;\n } else {\n break;\n }\n }\n\n if (!arrayItemElement) {\n this.focusInfo = null;\n return;\n }\n\n // Find the item index by looking at the item ID\n const itemIdMatch = arrayItemElement.id?.match(/array-item-(\\d+)/);\n if (!itemIdMatch) {\n this.focusInfo = null;\n return;\n }\n\n const itemIndex = parseInt(itemIdMatch[1], 10);\n\n // Determine the field name by examining the input element and its containers\n let fieldName = '';\n\n // First, check if it's a temba component with a name attribute\n if (targetElement.tagName?.toLowerCase().startsWith('temba-')) {\n fieldName =\n (targetElement as any).name || targetElement.getAttribute('name') || '';\n }\n\n // If not found, check regular HTML elements\n if (\n !fieldName &&\n targetElement.hasAttribute &&\n targetElement.hasAttribute('name')\n ) {\n fieldName = targetElement.getAttribute('name') || '';\n }\n\n // If still not found, look for data-field-name in parent containers\n if (!fieldName) {\n let searchElement = targetElement;\n while (searchElement && searchElement !== arrayItemElement) {\n if (\n searchElement.hasAttribute &&\n searchElement.hasAttribute('data-field-name')\n ) {\n fieldName = searchElement.getAttribute('data-field-name') || '';\n break;\n }\n searchElement = searchElement.parentElement;\n }\n }\n\n if (!fieldName) {\n this.focusInfo = null;\n return;\n }\n\n // Capture selection for text inputs (try the actual input element inside temba components)\n let inputForSelection = targetElement;\n if (targetElement.tagName?.toLowerCase().startsWith('temba-')) {\n // Look for the actual input element inside the temba component\n const innerInput =\n targetElement.shadowRoot?.querySelector('input, textarea') ||\n targetElement.querySelector('input, textarea');\n if (innerInput) {\n inputForSelection = innerInput as HTMLElement;\n }\n }\n\n const selectionStart = (inputForSelection as any).selectionStart;\n const selectionEnd = (inputForSelection as any).selectionEnd;\n\n this.focusInfo = {\n itemIndex,\n fieldName,\n selectionStart,\n selectionEnd\n };\n }\n\n // Restore focus after update\n private restoreFocus(): void {\n if (!this.focusInfo) {\n return;\n }\n\n const { itemIndex, fieldName, selectionStart, selectionEnd } =\n this.focusInfo;\n\n // Find the target element by array item index\n const arrayItemId = `array-item-${itemIndex}`;\n const arrayItemElement = this.shadowRoot?.getElementById(arrayItemId);\n\n if (!arrayItemElement) {\n // If the exact item doesn't exist (e.g., due to reordering), try to find by field name\n const allItems = this.shadowRoot?.querySelectorAll('.array-item');\n if (allItems && allItems.length > itemIndex) {\n const fallbackItem = allItems[itemIndex];\n if (fallbackItem) {\n this.attemptFocusRestore(\n fallbackItem as HTMLElement,\n fieldName,\n selectionStart,\n selectionEnd\n );\n }\n }\n this.focusInfo = null;\n return;\n }\n\n this.attemptFocusRestore(\n arrayItemElement,\n fieldName,\n selectionStart,\n selectionEnd\n );\n this.focusInfo = null;\n }\n\n private attemptFocusRestore(\n container: HTMLElement,\n fieldName: string,\n selectionStart?: number,\n selectionEnd?: number\n ): void {\n // Look for the field container first\n const fieldContainer = container.querySelector(\n `[data-field-name=\"${fieldName}\"]`\n );\n\n let targetElement: HTMLElement | null = null;\n\n if (fieldContainer) {\n // Look for temba components or input elements within the field container\n targetElement = fieldContainer.querySelector(\n 'temba-textinput, temba-completion, input, textarea'\n ) as HTMLElement;\n }\n\n // Fallback: search entire container\n if (!targetElement) {\n const selectors = [\n `temba-textinput[name=\"${fieldName}\"]`,\n `temba-completion[name=\"${fieldName}\"]`,\n `input[name=\"${fieldName}\"]`,\n `textarea[name=\"${fieldName}\"]`,\n `[name=\"${fieldName}\"]`\n ];\n\n for (const selector of selectors) {\n targetElement = container.querySelector(selector) as HTMLElement;\n if (targetElement) break;\n }\n }\n\n if (targetElement) {\n // Use multiple animation frames to ensure DOM is fully settled\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n try {\n targetElement.focus();\n\n // Restore selection if it's a text input\n if (selectionStart !== undefined && selectionEnd !== undefined) {\n // For temba components, we need to focus the inner input\n let inputForSelection = targetElement;\n if (targetElement.tagName?.toLowerCase().startsWith('temba-')) {\n const innerInput =\n targetElement.shadowRoot?.querySelector('input, textarea') ||\n targetElement.querySelector('input, textarea');\n if (innerInput && 'setSelectionRange' in innerInput) {\n inputForSelection = innerInput as any;\n }\n }\n\n if ('setSelectionRange' in inputForSelection) {\n (inputForSelection as any).setSelectionRange(\n selectionStart,\n selectionEnd\n );\n }\n }\n } catch (error) {\n // Ignore focus errors - element might not be focusable\n // Focus restoration failed, silently continue\n }\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 // Override Lit's update lifecycle methods for focus preservation\n protected willUpdate(changedProperties: Map<string, any>): void {\n super.willUpdate(changedProperties);\n\n // Capture focus before update if items are changing\n if (changedProperties.has('_items') || changedProperties.has('value')) {\n this.captureFocus();\n }\n }\n\n updated(changedProperties: Map<string, any>): void {\n super.updated(changedProperties);\n\n // Restore focus after update if items changed\n if (changedProperties.has('_items') || changedProperties.has('value')) {\n this.restoreFocus();\n }\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 data-field-name=\"${fieldName}\"\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\" style=\"flex-grow:1\"></div>`\n );\n }\n\n return html`\n <div class=\"array-item\" id=\"array-item-${index}\">\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"]}
@@ -22,6 +22,7 @@ export class Omnibox extends Select {
22
22
  this.contacts = false;
23
23
  this.placeholder = 'Select recipients';
24
24
  this.multi = true;
25
+ this.jsonValue = true;
25
26
  this.searchable = true;
26
27
  this.searchOnFocus = true;
27
28
  this.queryParam = 'search';
@@ -115,6 +116,9 @@ __decorate([
115
116
  __decorate([
116
117
  property({ type: Boolean })
117
118
  ], Omnibox.prototype, "multi", void 0);
119
+ __decorate([
120
+ property({ type: Boolean })
121
+ ], Omnibox.prototype, "jsonValue", void 0);
118
122
  __decorate([
119
123
  property({ type: Boolean })
120
124
  ], Omnibox.prototype, "searchable", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"Omnibox.js","sourceRoot":"","sources":["../../../../src/form/select/Omnibox.ts"],"names":[],"mappings":";AAAA,OAAO,EAAkB,IAAI,EAAkB,MAAM,KAAK,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAgB,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEnC,IAAK,QAGJ;AAHD,WAAK,QAAQ;IACX,2BAAe,CAAA;IACf,+BAAmB,CAAA;AACrB,CAAC,EAHI,QAAQ,KAAR,QAAQ,QAGZ;AAYD,MAAM,aAAa,GAAG;IACpB,KAAK,EAAE,wBAAwB;IAC/B,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,MAAM;CACjB,CAAC;AAEF,MAAM,OAAO,OAAQ,SAAQ,MAAkB;IAA/C;;QAEE,aAAQ,GAAG,MAAM,CAAC;QAGlB,WAAM,GAAG,KAAK,CAAC;QAGf,aAAQ,GAAG,KAAK,CAAC;QAGjB,gBAAW,GAAG,mBAAmB,CAAC;QAGlC,UAAK,GAAG,IAAI,CAAC;QAGb,eAAU,GAAG,IAAI,CAAC;QAGlB,kBAAa,GAAG,IAAI,CAAC;QAGrB,eAAU,GAAG,QAAQ,CAAC;IAwFxB,CAAC;IAtFQ,MAAM,CAAC,OAAuB;QACnC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAEtB,IACE,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAClD,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,EAC9B,CAAC;YACD,IAAI,KAAK,GAAG,SAAS,CAAC;YACtB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChB,KAAK,IAAI,GAAG,CAAC;YACf,CAAC;YAED,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClB,KAAK,IAAI,GAAG,CAAC;YACf,CAAC;YAED,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACxC,CAAC;IACH,CAAC;IAED,iCAAiC;IAC1B,mBAAmB,CAAC,MAAkB;QAC3C,OAAO,IAAI,CAAA;;yCAE0B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;+BAC9B,MAAM,CAAC,IAAI;;;;YAI9B,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;;;KAG/B,CAAC;IACJ,CAAC;IAEO,WAAW,CAAC,MAAkB;QACpC,MAAM,KAAK,GAAG,EAAE,GAAG,aAAa,EAAE,CAAC;QAEnC,IAAI,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnD,IAAI,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC/B,OAAO,IAAI,CAAA,cAAc,QAAQ,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,GAAG,QAAQ,CAAC;YACjE,CAAC;QACH,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,KAAK,EAAE,CAAC;YACnC,OAAO,IAAI,CAAA;qBACI,QAAQ,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,cAAc,EAAE;OAC9D,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,+CAA+C;IACxC,yBAAyB,CAAC,MAAkB;QACjD,OAAO,IAAI,CAAA;;;;;YAKH,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;;;;;;YAMpB,MAAM,CAAC,IAAI;;;;;YAKX,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;;;KAG/B,CAAC;IACJ,CAAC;IAEO,OAAO,CAAC,MAAkB;QAChC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,KAAK,EAAE,CAAC;YACnC,OAAO,IAAI,CAAA,qBAAqB,IAAI,CAAC,KAAK,iBAAiB,CAAC;QAC9D,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,OAAO,EAAE,CAAC;YACrC,OAAO,IAAI,CAAA,qBAAqB,IAAI,CAAC,OAAO,iBAAiB,CAAC;QAChE,CAAC;IACH,CAAC;CACF;AA7GC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;yCACT;AAGlB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;uCACb;AAGf;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;yCACX;AAGjB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;4CACO;AAGlC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;sCACf;AAGb;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;2CACV;AAGlB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;8CACP;AAGrB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;2CACN","sourcesContent":["import { TemplateResult, html, PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { styleMap } from 'lit-html/directives/style-map.js';\nimport { Select, SelectOption } from './Select';\nimport { Icon } from '../../Icons';\n\nenum OmniType {\n Group = 'group',\n Contact = 'contact'\n}\n\nexport interface OmniOption extends SelectOption {\n id: string;\n name: string;\n type: OmniType;\n urn?: string;\n count?: number;\n contact?: string;\n scheme?: string;\n}\n\nconst postNameStyle = {\n color: 'var(--color-text-dark)',\n padding: '0px 6px',\n fontSize: '12px'\n};\n\nexport class Omnibox extends Select<OmniOption> {\n @property({ type: String })\n valueKey = 'uuid';\n\n @property({ type: Boolean })\n groups = false;\n\n @property({ type: Boolean })\n contacts = false;\n\n @property({ type: String })\n placeholder = 'Select recipients';\n\n @property({ type: Boolean })\n multi = true;\n\n @property({ type: Boolean })\n searchable = true;\n\n @property({ type: Boolean })\n searchOnFocus = true;\n\n @property({ type: Boolean })\n queryParam = 'search';\n\n public update(changes: PropertyValues): void {\n super.update(changes);\n\n if (\n (changes.has('groups') || changes.has('contacts')) &&\n (this.groups || this.contacts)\n ) {\n let types = '&types=';\n if (this.groups) {\n types += 'g';\n }\n\n if (this.contacts) {\n types += 'c';\n }\n\n this.endpoint = this.endpoint + types;\n }\n }\n\n /** An option in the drop down */\n public renderOptionDefault(option: OmniOption): TemplateResult {\n return html`\n <div style=\"display:flex;\">\n <div style=\"margin-right: 8px\">${this.getIcon(option)}</div>\n <div style=\"flex: 1\">${option.name}</div>\n <div\n style=\"background: rgba(50, 50, 50, 0.15); margin-left: 5px; display: flex; align-items: center; border-radius: 4px\"\n >\n ${this.getPostName(option)}\n </div>\n </div>\n `;\n }\n\n private getPostName(option: OmniOption): TemplateResult {\n const style = { ...postNameStyle };\n\n if (option.urn && option.type === OmniType.Contact) {\n if (option.urn !== option.name) {\n return html`<div style=${styleMap(style)}>${option.urn}</div>`;\n }\n }\n\n if (option.type === OmniType.Group) {\n return html`\n <div style=${styleMap(style)}>${option.count.toLocaleString()}</div>\n `;\n }\n\n return null;\n }\n\n /** Selection in the multi-select select box */\n public renderSelectedItemDefault(option: OmniOption): TemplateResult {\n return html`\n <div\n style=\"flex:1 1 auto; text-overflow:ellipsis; overflow:hidden; white-space:nowrap; display: flex; align-items: stretch; color: var(--color-text-dark); font-size: 12px;\"\n >\n <div style=\"align-self: center; padding: 0px 7px; color: #bbb\">\n ${this.getIcon(option)}\n </div>\n <div\n class=\"name\"\n style=\"align-self: center; padding: 0px; font-size: 12px;\"\n >\n ${option.name}\n </div>\n <div\n style=\"background: rgba(100, 100, 100, 0.05); border-left: 1px solid rgba(100, 100, 100, 0.1); margin-left: 12px; display: flex; align-items: center\"\n >\n ${this.getPostName(option)}\n </div>\n </div>\n `;\n }\n\n private getIcon(option: OmniOption): TemplateResult {\n if (option.type === OmniType.Group) {\n return html`<temba-icon name=\"${Icon.group}\"></temba-icon>`;\n }\n\n if (option.type === OmniType.Contact) {\n return html`<temba-icon name=\"${Icon.contact}\"></temba-icon>`;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"Omnibox.js","sourceRoot":"","sources":["../../../../src/form/select/Omnibox.ts"],"names":[],"mappings":"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","sourcesContent":["import { TemplateResult, html, PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { styleMap } from 'lit-html/directives/style-map.js';\nimport { Select, SelectOption } from './Select';\nimport { Icon } from '../../Icons';\n\nenum OmniType {\n Group = 'group',\n Contact = 'contact'\n}\n\nexport interface OmniOption extends SelectOption {\n id: string;\n name: string;\n type: OmniType;\n urn?: string;\n count?: number;\n contact?: string;\n scheme?: string;\n}\n\nconst postNameStyle = {\n color: 'var(--color-text-dark)',\n padding: '0px 6px',\n fontSize: '12px'\n};\n\nexport class Omnibox extends Select<OmniOption> {\n @property({ type: String })\n valueKey = 'uuid';\n\n @property({ type: Boolean })\n groups = false;\n\n @property({ type: Boolean })\n contacts = false;\n\n @property({ type: String })\n placeholder = 'Select recipients';\n\n @property({ type: Boolean })\n multi = true;\n\n @property({ type: Boolean })\n jsonValue = true;\n\n @property({ type: Boolean })\n searchable = true;\n\n @property({ type: Boolean })\n searchOnFocus = true;\n\n @property({ type: Boolean })\n queryParam = 'search';\n\n public update(changes: PropertyValues): void {\n super.update(changes);\n\n if (\n (changes.has('groups') || changes.has('contacts')) &&\n (this.groups || this.contacts)\n ) {\n let types = '&types=';\n if (this.groups) {\n types += 'g';\n }\n\n if (this.contacts) {\n types += 'c';\n }\n\n this.endpoint = this.endpoint + types;\n }\n }\n\n /** An option in the drop down */\n public renderOptionDefault(option: OmniOption): TemplateResult {\n return html`\n <div style=\"display:flex;\">\n <div style=\"margin-right: 8px\">${this.getIcon(option)}</div>\n <div style=\"flex: 1\">${option.name}</div>\n <div\n style=\"background: rgba(50, 50, 50, 0.15); margin-left: 5px; display: flex; align-items: center; border-radius: 4px\"\n >\n ${this.getPostName(option)}\n </div>\n </div>\n `;\n }\n\n private getPostName(option: OmniOption): TemplateResult {\n const style = { ...postNameStyle };\n\n if (option.urn && option.type === OmniType.Contact) {\n if (option.urn !== option.name) {\n return html`<div style=${styleMap(style)}>${option.urn}</div>`;\n }\n }\n\n if (option.type === OmniType.Group) {\n return html`\n <div style=${styleMap(style)}>${option.count.toLocaleString()}</div>\n `;\n }\n\n return null;\n }\n\n /** Selection in the multi-select select box */\n public renderSelectedItemDefault(option: OmniOption): TemplateResult {\n return html`\n <div\n style=\"flex:1 1 auto; text-overflow:ellipsis; overflow:hidden; white-space:nowrap; display: flex; align-items: stretch; color: var(--color-text-dark); font-size: 12px;\"\n >\n <div style=\"align-self: center; padding: 0px 7px; color: #bbb\">\n ${this.getIcon(option)}\n </div>\n <div\n class=\"name\"\n style=\"align-self: center; padding: 0px; font-size: 12px;\"\n >\n ${option.name}\n </div>\n <div\n style=\"background: rgba(100, 100, 100, 0.05); border-left: 1px solid rgba(100, 100, 100, 0.1); margin-left: 12px; display: flex; align-items: center\"\n >\n ${this.getPostName(option)}\n </div>\n </div>\n `;\n }\n\n private getIcon(option: OmniOption): TemplateResult {\n if (option.type === OmniType.Group) {\n return html`<temba-icon name=\"${Icon.group}\"></temba-icon>`;\n }\n\n if (option.type === OmniType.Contact) {\n return html`<temba-icon name=\"${Icon.contact}\"></temba-icon>`;\n }\n }\n}\n"]}