@nyaruka/temba-components 0.130.4 → 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.
- package/CHANGELOG.md +14 -0
- package/demo/sortable-rules-demo.html +155 -0
- package/dist/temba-components.js +132 -142
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +13 -7
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/actions/send_msg.js +1 -0
- package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_groups.js +149 -1
- package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +1 -0
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js +81 -75
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/form/ArrayEditor.js +106 -28
- package/out-tsc/src/form/ArrayEditor.js.map +1 -1
- package/out-tsc/src/form/select/Select.js +21 -25
- package/out-tsc/src/form/select/Select.js.map +1 -1
- package/out-tsc/src/list/SortableList.js +214 -140
- package/out-tsc/src/list/SortableList.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +9 -5
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/test/nodes/split_by_groups.test.js +130 -0
- package/out-tsc/test/nodes/split_by_groups.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_response.test.js +149 -0
- package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
- package/out-tsc/test/temba-field-config.test.js +56 -0
- package/out-tsc/test/temba-field-config.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
- package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
- package/screenshots/truth/editor/wait.png +0 -0
- package/screenshots/truth/field-renderer/select-with-label.png +0 -0
- package/screenshots/truth/list/fields-dragging.png +0 -0
- package/screenshots/truth/list/sortable-dragging.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
- package/screenshots/truth/select/search-enabled.png +0 -0
- package/screenshots/truth/select/search-selected-focus.png +0 -0
- package/screenshots/truth/select/search-selected.png +0 -0
- package/screenshots/truth/templates/default.png +0 -0
- package/screenshots/truth/templates/unapproved.png +0 -0
- package/src/events.ts +6 -6
- package/src/flow/CanvasNode.ts +15 -13
- package/src/flow/actions/send_msg.ts +1 -0
- package/src/flow/nodes/split_by_groups.ts +190 -1
- package/src/flow/nodes/split_by_llm_categorize.ts +1 -0
- package/src/flow/nodes/wait_for_response.ts +98 -74
- package/src/form/ArrayEditor.ts +112 -28
- package/src/form/select/Select.ts +24 -25
- package/src/list/SortableList.ts +250 -149
- package/src/live/ContactChat.ts +11 -5
- package/test/nodes/split_by_groups.test.ts +165 -0
- package/test/nodes/wait_for_response.test.ts +182 -0
- package/test/temba-field-config.test.ts +69 -0
package/src/form/ArrayEditor.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { customElement, property } from 'lit/decorators.js';
|
|
|
3
3
|
import { FieldConfig } from '../flow/types';
|
|
4
4
|
import { BaseListEditor, ListItem } from './BaseListEditor';
|
|
5
5
|
import { FieldRenderer } from './FieldRenderer';
|
|
6
|
+
import '../list/SortableList';
|
|
7
|
+
import { Icon } from '../Icons';
|
|
6
8
|
|
|
7
9
|
@customElement('temba-array-editor')
|
|
8
10
|
export class TembaArrayEditor extends BaseListEditor<ListItem> {
|
|
@@ -23,6 +25,9 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
|
|
|
23
25
|
@property({ type: Function })
|
|
24
26
|
isEmptyItemFn?: (item: any) => boolean;
|
|
25
27
|
|
|
28
|
+
@property({ type: Boolean })
|
|
29
|
+
sortable = false;
|
|
30
|
+
|
|
26
31
|
@property({ type: Boolean })
|
|
27
32
|
maintainEmptyItem = true; // Enable by default for better UX
|
|
28
33
|
|
|
@@ -103,6 +108,77 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
|
|
|
103
108
|
this.updateValue(updatedItems);
|
|
104
109
|
}
|
|
105
110
|
|
|
111
|
+
private handleOrderChanged(event: CustomEvent): void {
|
|
112
|
+
const detail = event.detail;
|
|
113
|
+
|
|
114
|
+
// Handle swap-based logic from SortableList
|
|
115
|
+
if (detail.swap && Array.isArray(detail.swap) && detail.swap.length === 2) {
|
|
116
|
+
const [fromIdx, toIdx] = detail.swap;
|
|
117
|
+
|
|
118
|
+
// Only reorder if the indexes are different and valid
|
|
119
|
+
if (
|
|
120
|
+
fromIdx !== toIdx &&
|
|
121
|
+
fromIdx >= 0 &&
|
|
122
|
+
toIdx >= 0 &&
|
|
123
|
+
fromIdx < this._items.length &&
|
|
124
|
+
toIdx < this._items.length
|
|
125
|
+
) {
|
|
126
|
+
const updatedItems = [...this._items];
|
|
127
|
+
// Move the item using splice operations
|
|
128
|
+
const movedItem = updatedItems.splice(fromIdx, 1)[0];
|
|
129
|
+
updatedItems.splice(toIdx, 0, movedItem);
|
|
130
|
+
this.updateValue(updatedItems);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
renderWidget(): TemplateResult {
|
|
136
|
+
const items = this.displayItems;
|
|
137
|
+
|
|
138
|
+
const itemsContent = items.map((item, index) => {
|
|
139
|
+
const renderedItem = this.renderItem(item, index);
|
|
140
|
+
|
|
141
|
+
if (this.sortable && !this.isEmptyItem(item)) {
|
|
142
|
+
// Wrap non-empty items with sortable class and unique ID for drag-and-drop
|
|
143
|
+
return html`
|
|
144
|
+
<div class="sortable" id="array-item-${index}">${renderedItem}</div>
|
|
145
|
+
`;
|
|
146
|
+
} else {
|
|
147
|
+
// Non-sortable items or empty items don't get the sortable wrapper
|
|
148
|
+
return renderedItem;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (this.sortable) {
|
|
153
|
+
return html`
|
|
154
|
+
<div class=${this.getContainerClass()}>
|
|
155
|
+
<temba-sortable-list
|
|
156
|
+
dragHandle="drag-handle"
|
|
157
|
+
gap="0.4em"
|
|
158
|
+
@temba-order-changed=${this.handleOrderChanged}
|
|
159
|
+
style="display: grid; grid-template-columns: 1fr; gap: 8px;"
|
|
160
|
+
>
|
|
161
|
+
${itemsContent}
|
|
162
|
+
</temba-sortable-list>
|
|
163
|
+
${this.shouldShowAddButton() ? this.renderAddButton() : ''}
|
|
164
|
+
</div>
|
|
165
|
+
`;
|
|
166
|
+
} else {
|
|
167
|
+
// Non-sortable rendering (original behavior)
|
|
168
|
+
return html`
|
|
169
|
+
<div class=${this.getContainerClass()}>
|
|
170
|
+
<div
|
|
171
|
+
class="list-items"
|
|
172
|
+
style="display: grid; grid-template-columns: 1fr; gap: 8px;"
|
|
173
|
+
>
|
|
174
|
+
${itemsContent}
|
|
175
|
+
</div>
|
|
176
|
+
${this.shouldShowAddButton() ? this.renderAddButton() : ''}
|
|
177
|
+
</div>
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
106
182
|
private computeFieldValue(
|
|
107
183
|
itemIndex: number,
|
|
108
184
|
fieldName: string,
|
|
@@ -210,11 +286,9 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
|
|
|
210
286
|
|
|
211
287
|
fieldElements.push(html`
|
|
212
288
|
<div
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
? 'field-fixed'
|
|
217
|
-
: 'field-flex'}"
|
|
289
|
+
style="${config.width || config.maxWidth || config.type === 'select'
|
|
290
|
+
? 'flex:none'
|
|
291
|
+
: 'flex:1'}"
|
|
218
292
|
>
|
|
219
293
|
${this.renderArrayField(index, fieldName, config)}
|
|
220
294
|
</div>
|
|
@@ -234,11 +308,31 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
|
|
|
234
308
|
|
|
235
309
|
return html`
|
|
236
310
|
<div class="array-item">
|
|
237
|
-
<div
|
|
311
|
+
<div
|
|
312
|
+
class="item-fields ${canRemove ? '' : 'removable'}"
|
|
313
|
+
style="display: flex; gap: 12px; align-items: center"
|
|
314
|
+
>
|
|
315
|
+
${this.sortable
|
|
316
|
+
? html`<temba-icon
|
|
317
|
+
name=${Icon.sort}
|
|
318
|
+
style="margin-right: -6px;"
|
|
319
|
+
class="drag-handle"
|
|
320
|
+
></temba-icon>`
|
|
321
|
+
: null}
|
|
238
322
|
${fieldElements}
|
|
239
323
|
<button
|
|
240
324
|
@click=${canRemove ? () => this.removeItem(index) : undefined}
|
|
241
|
-
class="remove-btn
|
|
325
|
+
class="remove-btn"
|
|
326
|
+
style="
|
|
327
|
+
padding: 4px;
|
|
328
|
+
border: 1px solid #ccc;
|
|
329
|
+
border-radius: 4px;
|
|
330
|
+
background: white;
|
|
331
|
+
cursor: pointer;
|
|
332
|
+
background: #fefefe;
|
|
333
|
+
color: #999;
|
|
334
|
+
font-size: 14px;
|
|
335
|
+
"
|
|
242
336
|
?disabled=${!canRemove}
|
|
243
337
|
>
|
|
244
338
|
<temba-icon name="x"></temba-icon>
|
|
@@ -256,12 +350,6 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
|
|
|
256
350
|
return css`
|
|
257
351
|
${super.styles}
|
|
258
352
|
|
|
259
|
-
.array-editor {
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
.array-item {
|
|
263
|
-
}
|
|
264
|
-
|
|
265
353
|
.item-header {
|
|
266
354
|
display: flex;
|
|
267
355
|
justify-content: space-between;
|
|
@@ -273,24 +361,10 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
|
|
|
273
361
|
color: #333;
|
|
274
362
|
}
|
|
275
363
|
|
|
276
|
-
.item-fields {
|
|
277
|
-
display: flex;
|
|
278
|
-
gap: 12px;
|
|
279
|
-
align-items: center;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
364
|
.field {
|
|
283
365
|
/* Base field styles */
|
|
284
366
|
}
|
|
285
367
|
|
|
286
|
-
.field-flex {
|
|
287
|
-
flex: 1; /* Grow to fill remaining space */
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
.field-fixed {
|
|
291
|
-
flex: none; /* Don't grow, use content/maxWidth size */
|
|
292
|
-
}
|
|
293
|
-
|
|
294
368
|
.spacer {
|
|
295
369
|
/* Empty spacer to maintain layout alignment */
|
|
296
370
|
}
|
|
@@ -315,10 +389,20 @@ export class TembaArrayEditor extends BaseListEditor<ListItem> {
|
|
|
315
389
|
color: #999;
|
|
316
390
|
}
|
|
317
391
|
|
|
318
|
-
.remove-btn
|
|
392
|
+
.removable .remove-btn {
|
|
319
393
|
visibility: hidden;
|
|
320
394
|
cursor: default;
|
|
321
395
|
}
|
|
396
|
+
|
|
397
|
+
.removable .drag-handle {
|
|
398
|
+
visibility: hidden;
|
|
399
|
+
cursor: default;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.drag-handle {
|
|
403
|
+
cursor: grab;
|
|
404
|
+
color: #ccc;
|
|
405
|
+
}
|
|
322
406
|
`;
|
|
323
407
|
}
|
|
324
408
|
}
|
|
@@ -247,13 +247,15 @@ export class Select<T extends SelectOption> extends FieldElement {
|
|
|
247
247
|
|
|
248
248
|
.input-wrapper:focus-within {
|
|
249
249
|
min-width: 1px;
|
|
250
|
+
display: flex;
|
|
250
251
|
}
|
|
251
252
|
|
|
252
253
|
.input-wrapper {
|
|
253
254
|
min-width: 1px;
|
|
254
255
|
margin-left: 6px;
|
|
255
256
|
margin-right: -6px;
|
|
256
|
-
display:
|
|
257
|
+
display: none;
|
|
258
|
+
pointer-events: none;
|
|
257
259
|
}
|
|
258
260
|
|
|
259
261
|
.multi .input-wrapper {
|
|
@@ -312,6 +314,7 @@ export class Select<T extends SelectOption> extends FieldElement {
|
|
|
312
314
|
display: none;
|
|
313
315
|
line-height: var(--temba-select-selected-line-height);
|
|
314
316
|
margin-left: 6px;
|
|
317
|
+
pointer-events: none;
|
|
315
318
|
}
|
|
316
319
|
|
|
317
320
|
.empty .placeholder {
|
|
@@ -371,10 +374,6 @@ export class Select<T extends SelectOption> extends FieldElement {
|
|
|
371
374
|
pointer-events: none;
|
|
372
375
|
padding: 0px;
|
|
373
376
|
}
|
|
374
|
-
|
|
375
|
-
.ghost .remove-item {
|
|
376
|
-
display: none !important;
|
|
377
|
-
}
|
|
378
377
|
`;
|
|
379
378
|
}
|
|
380
379
|
|
|
@@ -1566,6 +1565,7 @@ export class Select<T extends SelectOption> extends FieldElement {
|
|
|
1566
1565
|
public removeValue(valueToRemove: any) {
|
|
1567
1566
|
const oldValues = [...this.values];
|
|
1568
1567
|
const idx = this.values.indexOf(valueToRemove);
|
|
1568
|
+
|
|
1569
1569
|
if (idx > -1) {
|
|
1570
1570
|
this.values.splice(idx, 1);
|
|
1571
1571
|
|
|
@@ -1683,10 +1683,13 @@ export class Select<T extends SelectOption> extends FieldElement {
|
|
|
1683
1683
|
|
|
1684
1684
|
const input = this.searchable
|
|
1685
1685
|
? html`
|
|
1686
|
-
<div
|
|
1686
|
+
<div
|
|
1687
|
+
class="input-wrapper"
|
|
1688
|
+
style="${this.focused ? 'display:flex;' : ''}"
|
|
1689
|
+
>
|
|
1687
1690
|
<input
|
|
1688
1691
|
class="searchbox"
|
|
1689
|
-
style
|
|
1692
|
+
style="${this.inputStyle ? styleMap(this.inputStyle) : ''};"
|
|
1690
1693
|
@input=${this.handleInput}
|
|
1691
1694
|
@keydown=${this.handleKeyDown}
|
|
1692
1695
|
@click=${this.handleClick}
|
|
@@ -1704,15 +1707,11 @@ export class Select<T extends SelectOption> extends FieldElement {
|
|
|
1704
1707
|
<temba-sortable-list
|
|
1705
1708
|
horizontal
|
|
1706
1709
|
@temba-order-changed=${this.handleOrderChanged}
|
|
1707
|
-
.prepareGhost=${(item: any) => {
|
|
1708
|
-
item.style.transform = 'scale(1)';
|
|
1709
|
-
item.querySelector('.remove-item').style.display = 'none';
|
|
1710
|
-
}}
|
|
1711
1710
|
>
|
|
1712
1711
|
${this.values.map(
|
|
1713
1712
|
(selected: any, index: number) => html`
|
|
1714
1713
|
<div
|
|
1715
|
-
class="selected-item
|
|
1714
|
+
class="sortable selected-item ${index === this.selectedIndex
|
|
1716
1715
|
? 'focused'
|
|
1717
1716
|
: ''} ${this.draggingId === `selected-${index}`
|
|
1718
1717
|
? 'dragging'
|
|
@@ -1770,7 +1769,7 @@ export class Select<T extends SelectOption> extends FieldElement {
|
|
|
1770
1769
|
</div>
|
|
1771
1770
|
`
|
|
1772
1771
|
)}
|
|
1773
|
-
${
|
|
1772
|
+
${input}
|
|
1774
1773
|
</temba-sortable-list>
|
|
1775
1774
|
`
|
|
1776
1775
|
: html`${this.values.map(
|
|
@@ -1794,15 +1793,9 @@ export class Select<T extends SelectOption> extends FieldElement {
|
|
|
1794
1793
|
? html`
|
|
1795
1794
|
<div
|
|
1796
1795
|
class="remove-item"
|
|
1797
|
-
style="
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
padding: 3px 6px;
|
|
1801
|
-
border-right: 1px solid rgba(100,100,100,0.2);
|
|
1802
|
-
margin: 0;
|
|
1803
|
-
background: rgba(100,100,100,0.05);
|
|
1804
|
-
margin-top:1px;
|
|
1805
|
-
"
|
|
1796
|
+
style="cursor: pointer; display: inline-block; padding: 3px 6px;
|
|
1797
|
+
border-right: 1px solid rgba(100,100,100,0.2);
|
|
1798
|
+
margin: 0; background: rgba(100,100,100,0.05); margin-top:1px;"
|
|
1806
1799
|
@click=${(evt: MouseEvent) => {
|
|
1807
1800
|
evt.preventDefault();
|
|
1808
1801
|
evt.stopPropagation();
|
|
@@ -1834,9 +1827,9 @@ export class Select<T extends SelectOption> extends FieldElement {
|
|
|
1834
1827
|
class="select-container ${classes}"
|
|
1835
1828
|
@click=${this.handleContainerClick}
|
|
1836
1829
|
>
|
|
1837
|
-
<div class="left-side"
|
|
1830
|
+
<div class="left-side">
|
|
1838
1831
|
<slot name="prefix"></slot>
|
|
1839
|
-
<div class="selected"
|
|
1832
|
+
<div class="selected">
|
|
1840
1833
|
${
|
|
1841
1834
|
this.resolving
|
|
1842
1835
|
? html`<temba-loading
|
|
@@ -1851,7 +1844,13 @@ export class Select<T extends SelectOption> extends FieldElement {
|
|
|
1851
1844
|
|
|
1852
1845
|
${clear}
|
|
1853
1846
|
|
|
1854
|
-
<slot name="right"
|
|
1847
|
+
<slot name="right">${
|
|
1848
|
+
this.fetching
|
|
1849
|
+
? html`<temba-loading
|
|
1850
|
+
style="position:absolute;background:rgba(255,255,255,0.7);padding:2px;border-radius:var(--curvature);right:25px"
|
|
1851
|
+
></temba-loading>`
|
|
1852
|
+
: null
|
|
1853
|
+
}</slot>
|
|
1855
1854
|
${
|
|
1856
1855
|
!this.tags && !this.emails
|
|
1857
1856
|
? html`<div
|