@nyaruka/temba-components 0.123.0 → 0.124.1

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 (146) hide show
  1. package/.github/copilot-instructions.md +22 -4
  2. package/CHANGELOG.md +21 -0
  3. package/TEST_OPTIMIZATION.md +158 -0
  4. package/demo/alert/example.html +65 -0
  5. package/demo/button/example.html +71 -0
  6. package/demo/chart/example.html +56 -0
  7. package/demo/checkbox/example.html +72 -0
  8. package/demo/compose/example.html +72 -0
  9. package/demo/data/images/gus.png +0 -0
  10. package/demo/data/images/purrington.jpg +0 -0
  11. package/demo/data/server/opened-tickets.json +40 -0
  12. package/demo/data/server/response-time.json +27 -0
  13. package/demo/datepicker/example.html +69 -0
  14. package/demo/dialog/example.html +107 -0
  15. package/demo/dropdown/example.html +99 -0
  16. package/demo/index.html +152 -430
  17. package/demo/misc/example.html +72 -0
  18. package/demo/progress/example.html +59 -0
  19. package/demo/select/drag-and-drop.html +142 -0
  20. package/demo/select/example.html +82 -0
  21. package/demo/select/multi.html +73 -0
  22. package/demo/slider/example.html +59 -0
  23. package/demo/sortable-list/example.html +99 -0
  24. package/demo/styles.css +183 -0
  25. package/demo/tabs/example.html +91 -0
  26. package/demo/textinput/completion.html +56 -0
  27. package/demo/textinput/example.html +61 -0
  28. package/dist/temba-components.js +323 -191
  29. package/dist/temba-components.js.map +1 -1
  30. package/out-tsc/src/chart/TembaChart.js +19 -16
  31. package/out-tsc/src/chart/TembaChart.js.map +1 -1
  32. package/out-tsc/src/fields/FieldManager.js +27 -34
  33. package/out-tsc/src/fields/FieldManager.js.map +1 -1
  34. package/out-tsc/src/flow/Editor.js +1 -1
  35. package/out-tsc/src/flow/Editor.js.map +1 -1
  36. package/out-tsc/src/list/SortableList.js +257 -60
  37. package/out-tsc/src/list/SortableList.js.map +1 -1
  38. package/out-tsc/src/omnibox/Omnibox.js +1 -1
  39. package/out-tsc/src/omnibox/Omnibox.js.map +1 -1
  40. package/out-tsc/src/select/Select.js +198 -38
  41. package/out-tsc/src/select/Select.js.map +1 -1
  42. package/out-tsc/src/thumbnail/Thumbnail.js +1 -1
  43. package/out-tsc/src/thumbnail/Thumbnail.js.map +1 -1
  44. package/out-tsc/src/webchat/WebChat.js +5 -2
  45. package/out-tsc/src/webchat/WebChat.js.map +1 -1
  46. package/out-tsc/test/temba-chart.test.js +1 -1
  47. package/out-tsc/test/temba-chart.test.js.map +1 -1
  48. package/out-tsc/test/temba-compose.test.js +6 -30
  49. package/out-tsc/test/temba-compose.test.js.map +1 -1
  50. package/out-tsc/test/temba-contact-chat.test.js +1 -2
  51. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  52. package/out-tsc/test/temba-dropdown.test.js +1 -1
  53. package/out-tsc/test/temba-dropdown.test.js.map +1 -1
  54. package/out-tsc/test/temba-flow-editor-node.test.js +273 -0
  55. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -0
  56. package/out-tsc/test/temba-flow-editor.test.js +244 -0
  57. package/out-tsc/test/temba-flow-editor.test.js.map +1 -0
  58. package/out-tsc/test/temba-flow-plumber.test.js +145 -0
  59. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -0
  60. package/out-tsc/test/temba-flow-render.test.js +171 -0
  61. package/out-tsc/test/temba-flow-render.test.js.map +1 -0
  62. package/out-tsc/test/temba-omnibox.test.js +6 -3
  63. package/out-tsc/test/temba-omnibox.test.js.map +1 -1
  64. package/out-tsc/test/temba-select.test.js +183 -53
  65. package/out-tsc/test/temba-select.test.js.map +1 -1
  66. package/out-tsc/test/temba-sortable-list.test.js +91 -15
  67. package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
  68. package/out-tsc/test/temba-toast.test.js +1 -2
  69. package/out-tsc/test/temba-toast.test.js.map +1 -1
  70. package/out-tsc/test/temba-utils-index.test.js +2 -2
  71. package/out-tsc/test/temba-utils-index.test.js.map +1 -1
  72. package/out-tsc/test/temba-webchat-lightbox-fix.test.js +42 -0
  73. package/out-tsc/test/temba-webchat-lightbox-fix.test.js.map +1 -0
  74. package/out-tsc/test/utils.test.js +58 -0
  75. package/out-tsc/test/utils.test.js.map +1 -1
  76. package/package.json +2 -3
  77. package/screenshots/truth/flow/editor-basic.png +0 -0
  78. package/screenshots/truth/list/fields-dragging.png +0 -0
  79. package/screenshots/truth/list/sortable-dragging.png +0 -0
  80. package/screenshots/truth/list/sortable-dropped.png +0 -0
  81. package/screenshots/truth/list/sortable.png +0 -0
  82. package/screenshots/truth/omnibox/selected.png +0 -0
  83. package/screenshots/truth/select/disabled-multi-selection.png +0 -0
  84. package/screenshots/truth/select/disabled-selection.png +0 -0
  85. package/screenshots/truth/select/disabled.png +0 -0
  86. package/screenshots/truth/select/embedded.png +0 -0
  87. package/screenshots/truth/select/empty-options.png +0 -0
  88. package/screenshots/truth/select/expression-selected.png +0 -0
  89. package/screenshots/truth/select/expressions.png +0 -0
  90. package/screenshots/truth/select/functions.png +0 -0
  91. package/screenshots/truth/select/local-options.png +0 -0
  92. package/screenshots/truth/select/multi-reorder-final.png +0 -0
  93. package/screenshots/truth/select/multi-reorder-initial.png +0 -0
  94. package/screenshots/truth/select/multi-with-endpoint.png +0 -0
  95. package/screenshots/truth/select/multiple-initial-values.png +0 -0
  96. package/screenshots/truth/select/remote-options.png +0 -0
  97. package/screenshots/truth/select/search-enabled.png +0 -0
  98. package/screenshots/truth/select/search-multi-no-matches.png +0 -0
  99. package/screenshots/truth/select/search-selected-focus.png +0 -0
  100. package/screenshots/truth/select/search-selected.png +0 -0
  101. package/screenshots/truth/select/search-with-selected.png +0 -0
  102. package/screenshots/truth/select/searching.png +0 -0
  103. package/screenshots/truth/select/selected-multi-maxitems-reached.png +0 -0
  104. package/screenshots/truth/select/selected-multi.png +0 -0
  105. package/screenshots/truth/select/selected-single.png +0 -0
  106. package/screenshots/truth/select/selection-clearable.png +0 -0
  107. package/screenshots/truth/select/static-initial-value.png +0 -0
  108. package/screenshots/truth/select/static-initial-via-selected.png +0 -0
  109. package/screenshots/truth/select/truncated-selection.png +0 -0
  110. package/screenshots/truth/select/with-placeholder.png +0 -0
  111. package/screenshots/truth/select/without-placeholder.png +0 -0
  112. package/screenshots/truth/templates/default.png +0 -0
  113. package/screenshots/truth/templates/unapproved.png +0 -0
  114. package/screenshots/truth/webchat/connected-state.png +0 -0
  115. package/src/chart/TembaChart.ts +20 -16
  116. package/src/fields/FieldManager.ts +30 -38
  117. package/src/flow/Editor.ts +1 -1
  118. package/src/list/SortableList.ts +291 -67
  119. package/src/omnibox/Omnibox.ts +1 -1
  120. package/src/select/Select.ts +213 -42
  121. package/src/thumbnail/Thumbnail.ts +1 -1
  122. package/src/webchat/WebChat.ts +5 -2
  123. package/test/temba-chart.test.ts +1 -1
  124. package/test/temba-compose.test.ts +11 -38
  125. package/test/temba-contact-chat.test.ts +4 -6
  126. package/test/temba-dropdown.test.ts +1 -1
  127. package/test/temba-flow-editor-node.test.ts +344 -0
  128. package/test/temba-flow-editor.test.ts +301 -0
  129. package/test/temba-flow-plumber.test.ts +189 -0
  130. package/test/temba-flow-render.test.ts +220 -0
  131. package/test/temba-omnibox.test.ts +7 -3
  132. package/test/temba-select.test.ts +247 -79
  133. package/test/temba-sortable-list.test.ts +108 -15
  134. package/test/temba-toast.test.ts +2 -2
  135. package/test/temba-utils-index.test.ts +2 -2
  136. package/test/temba-webchat-lightbox-fix.test.ts +57 -0
  137. package/test/utils.test.ts +88 -0
  138. package/web-test-runner.config.mjs +4 -2
  139. package/.storybook/main.js +0 -14
  140. package/.storybook/preview-head.html +0 -1
  141. package/.storybook/preview.js +0 -17
  142. package/demo/agents.html +0 -147
  143. package/demo/old.html +0 -573
  144. package/demo/remote.html +0 -3
  145. package/screenshots/truth/compose/attachments-with-files-focused.png +0 -0
  146. package/stories/temba-checkbox.stories.md +0 -37
@@ -4,6 +4,7 @@ import { html, css } from 'lit';
4
4
  import { property, state } from 'lit/decorators.js';
5
5
  import { getUrl, getClasses, fetchResults, postJSON } from '../utils';
6
6
  import '../options/Options';
7
+ import '../list/SortableList';
7
8
  import { FormElement } from '../FormElement';
8
9
  import { lru } from 'tiny-lru';
9
10
  import { CustomEventType } from '../interfaces';
@@ -52,7 +53,7 @@ export class Select extends FormElement {
52
53
  background: rgba(100, 100, 100, 0.05);
53
54
  }
54
55
 
55
- .selected-item.multi .remove-item {
56
+ . selected-item.multi .remove-item {
56
57
  display: none;
57
58
  }
58
59
 
@@ -88,7 +89,7 @@ export class Select extends FormElement {
88
89
  padding-top: 1px;
89
90
  box-shadow: var(--widget-box-shadow);
90
91
  position: relative;
91
- min-height: var(--temba-select-min-height, 2.5em);
92
+ min-height: var(--temba-select-min-height, 2.4em);
92
93
  }
93
94
 
94
95
  temba-icon.select-open:hover,
@@ -124,7 +125,7 @@ export class Select extends FormElement {
124
125
  flex-direction: row;
125
126
  align-items: stretch;
126
127
  user-select: none;
127
- padding: var(--temba-select-selected-padding);
128
+ padding: var(--temba-select-selected-padding, 0px 4px);
128
129
  }
129
130
 
130
131
  .searchable .selected {
@@ -183,6 +184,18 @@ export class Select extends FormElement {
183
184
  background: rgba(100, 100, 100, 0.3);
184
185
  }
185
186
 
187
+ .multi .selected-item.sortable {
188
+ cursor: move;
189
+ }
190
+
191
+ .multi .selected-item.dragging {
192
+ opacity: 0.5;
193
+ }
194
+
195
+ .multi temba-sortable-list {
196
+ margin: 0 !important;
197
+ }
198
+
186
199
  input {
187
200
  font-size: 13px;
188
201
  width: 0px;
@@ -201,16 +214,15 @@ export class Select extends FormElement {
201
214
  border: 0px solid purple !important;
202
215
  }
203
216
 
204
- .input-wrapper {
205
- min-width: 1px;
206
- }
207
-
208
217
  .input-wrapper:focus-within {
209
218
  min-width: 1px;
210
219
  }
211
220
 
212
221
  .input-wrapper {
222
+ min-width: 1px;
213
223
  margin-left: 6px;
224
+ margin-right: -6px;
225
+ display: flex;
214
226
  }
215
227
 
216
228
  .multi .input-wrapper {
@@ -250,9 +262,9 @@ export class Select extends FormElement {
250
262
  box-shadow: none !important;
251
263
  }
252
264
 
253
- .input-wrapper {
254
- display: flex;
255
- margin-right: 0em;
265
+ .multi .input-wrapper {
266
+ flex-shrink: 0;
267
+ min-width: 100px;
256
268
  }
257
269
 
258
270
  .input-wrapper .searchbox {
@@ -267,6 +279,7 @@ export class Select extends FormElement {
267
279
  color: var(--color-placeholder);
268
280
  display: none;
269
281
  line-height: var(--temba-select-selected-line-height);
282
+ margin-left: 6px;
270
283
  }
271
284
 
272
285
  .footer {
@@ -308,6 +321,10 @@ export class Select extends FormElement {
308
321
  pointer-events: none;
309
322
  padding: 0px;
310
323
  }
324
+
325
+ .ghost .remove-item {
326
+ display: none !important;
327
+ }
311
328
  `;
312
329
  }
313
330
  constructor() {
@@ -368,6 +385,7 @@ export class Select extends FormElement {
368
385
  this.renderSelectedItemDefault = this.renderSelectedItemDefault.bind(this);
369
386
  this.prepareOptionsDefault = this.prepareOptionsDefault.bind(this);
370
387
  this.isMatchDefault = this.isMatchDefault.bind(this);
388
+ this.handleOrderChanged = this.handleOrderChanged.bind(this);
371
389
  }
372
390
  prepareOptionsDefault(options) {
373
391
  return options;
@@ -1032,8 +1050,12 @@ export class Select extends FormElement {
1032
1050
  this.cursorIndex = event.detail.index;
1033
1051
  }
1034
1052
  handleContainerClick(event) {
1035
- event.stopPropagation();
1036
- event.preventDefault();
1053
+ if (this.disabled) {
1054
+ // prevent opening dropdown right after drag-and-drop
1055
+ event.stopPropagation();
1056
+ event.preventDefault();
1057
+ return;
1058
+ }
1037
1059
  this.focused = true;
1038
1060
  if (event.target.tagName !== 'INPUT') {
1039
1061
  const input = this.shadowRoot.querySelector('input');
@@ -1067,6 +1089,9 @@ export class Select extends FormElement {
1067
1089
  ];
1068
1090
  }
1069
1091
  handleArrowClick(event) {
1092
+ if (this.disabled) {
1093
+ return;
1094
+ }
1070
1095
  if (this.isOpen()) {
1071
1096
  event.preventDefault();
1072
1097
  event.stopPropagation();
@@ -1080,7 +1105,16 @@ export class Select extends FormElement {
1080
1105
  // special case for icons on any option type
1081
1106
  const icon = option.icon;
1082
1107
  return html `
1083
- <div class="option-name" style="display:flex">
1108
+ <div
1109
+ class="option-name"
1110
+ style="flex: 1 1 auto;
1111
+ align-self: center;
1112
+ white-space: nowrap;
1113
+ overflow: hidden;
1114
+ text-overflow: ellipsis;
1115
+ padding: 2px 8px;
1116
+ display: flex;"
1117
+ >
1084
1118
  ${icon
1085
1119
  ? html `<temba-icon
1086
1120
  name="${icon}"
@@ -1138,6 +1172,17 @@ export class Select extends FormElement {
1138
1172
  const idx = this.values.indexOf(valueToRemove);
1139
1173
  if (idx > -1) {
1140
1174
  this.values.splice(idx, 1);
1175
+ // Also remove the 'selected' attribute from the corresponding temba-option element
1176
+ const valueToMatch = this.getValue(valueToRemove);
1177
+ for (const child of this.children) {
1178
+ if (child.tagName === 'TEMBA-OPTION') {
1179
+ const childValue = child.getAttribute('value');
1180
+ if (childValue === valueToMatch) {
1181
+ child.removeAttribute('selected');
1182
+ break;
1183
+ }
1184
+ }
1185
+ }
1141
1186
  }
1142
1187
  this.requestUpdate('values', oldValues);
1143
1188
  this.infoText = '';
@@ -1161,6 +1206,25 @@ export class Select extends FormElement {
1161
1206
  this.staticOptions.length === 0 &&
1162
1207
  !this.endpoint);
1163
1208
  }
1209
+ handleOrderChanged(event) {
1210
+ const detail = event.detail;
1211
+ // Handle new swap-based format
1212
+ if (detail.swap && Array.isArray(detail.swap) && detail.swap.length === 2) {
1213
+ const [fromIdx, toIdx] = detail.swap;
1214
+ // Only reorder if the indexes are different and valid
1215
+ if (fromIdx !== toIdx &&
1216
+ fromIdx >= 0 &&
1217
+ toIdx >= 0 &&
1218
+ fromIdx < this.values.length &&
1219
+ toIdx < this.values.length) {
1220
+ const oldValues = [...this.values];
1221
+ // Move the item from fromIdx to toIdx
1222
+ const movedItem = this.values.splice(fromIdx, 1)[0];
1223
+ this.values.splice(toIdx, 0, movedItem);
1224
+ this.requestUpdate('values', oldValues);
1225
+ }
1226
+ }
1227
+ }
1164
1228
  render() {
1165
1229
  const placeholder = this.values.length === 0 ? this.placeholder : '';
1166
1230
  const placeholderDiv = html `
@@ -1237,33 +1301,126 @@ export class Select extends FormElement {
1237
1301
  ></temba-loading>`
1238
1302
  : null}
1239
1303
  ${!this.multi && !this.resolving ? input : null}
1240
- ${this.values.map((selected, index) => html `
1241
- <div
1242
- class="selected-item ${index === this.selectedIndex
1243
- ? 'focused'
1244
- : ''}"
1245
- >
1246
- ${this.multi
1304
+ ${this.multi && this.values.length > 1
1247
1305
  ? html `
1248
- <div
1249
- class="remove-item"
1250
- style="margin-top:1px"
1251
- @click=${(evt) => {
1252
- evt.preventDefault();
1253
- evt.stopPropagation();
1254
- this.handleRemoveSelection(selected);
1306
+ <temba-sortable-list
1307
+ horizontal
1308
+ @temba-order-changed=${this.handleOrderChanged}
1309
+ .prepareGhost=${(item) => {
1310
+ item.style.transform = 'scale(1)';
1311
+ item.querySelector('.remove-item').style.display =
1312
+ 'none';
1255
1313
  }}
1256
- >
1257
- <temba-icon
1258
- name="${Icon.delete_small}"
1259
- size="1"
1260
- ></temba-icon>
1261
- </div>
1262
- `
1263
- : null}
1264
- ${this.renderSelectedItem(selected)}
1265
- </div>
1266
- `)}
1314
+ >
1315
+ ${this.values.map((selected, index) => html `
1316
+ <div
1317
+ class="selected-item sortable ${index ===
1318
+ this.selectedIndex
1319
+ ? 'focused'
1320
+ : ''} ${this.draggingId === `selected-${index}`
1321
+ ? 'dragging'
1322
+ : ''}"
1323
+ id="selected-${index}"
1324
+ style="
1325
+ vertical-align: middle;
1326
+ background: rgba(100,100,100,0.1);
1327
+ user-select: none;
1328
+ border-radius: 2px;
1329
+ align-items: center;
1330
+ flex-direction: row;
1331
+ flex-wrap: nowrap;
1332
+ margin: 2px 2px;
1333
+ display: flex;
1334
+ overflow: hidden;
1335
+ color: var(--color-widget-text);
1336
+ line-height: var(--temba-select-selected-line-height);
1337
+ --icon-color: var(--color-text-dark);
1338
+ ${index === this.selectedIndex
1339
+ ? 'background: rgba(100,100,100,0.3);'
1340
+ : ''}
1341
+ ${this.draggingId === `selected-${index}`
1342
+ ? 'opacity: 0.5;'
1343
+ : ''}
1344
+ "
1345
+ >
1346
+ ${this.multi
1347
+ ? html `
1348
+ <div
1349
+ class="remove-item"
1350
+ style="
1351
+ cursor: pointer;
1352
+ display: inline-block;
1353
+ padding: 3px 6px;
1354
+ border-right: 1px solid rgba(100,100,100,0.2);
1355
+ margin: 0;
1356
+ background: rgba(100,100,100,0.05);
1357
+ margin-top:1px;
1358
+ "
1359
+ @click=${(evt) => {
1360
+ evt.preventDefault();
1361
+ evt.stopPropagation();
1362
+ this.handleRemoveSelection(selected);
1363
+ }}
1364
+ >
1365
+ <temba-icon
1366
+ name="${Icon.delete_small}"
1367
+ size="1"
1368
+ ></temba-icon>
1369
+ </div>
1370
+ `
1371
+ : null}
1372
+ ${this.renderSelectedItem(selected)}
1373
+ </div>
1374
+ `)}
1375
+ </temba-sortable-list>
1376
+ `
1377
+ : this.values.map((selected, index) => html `
1378
+ <div
1379
+ class="selected-item ${index === this.selectedIndex
1380
+ ? 'focused'
1381
+ : ''}"
1382
+ style="
1383
+ display: flex;
1384
+ overflow: hidden;
1385
+ color: var(--color-widget-text);
1386
+ line-height: var(--temba-select-selected-line-height);
1387
+ --icon-color: var(--color-text-dark);
1388
+ ${index === this.selectedIndex
1389
+ ? 'background: rgba(100,100,100,0.3);'
1390
+ : ''}
1391
+ "
1392
+ >
1393
+ ${this.multi
1394
+ ? html `
1395
+ <div
1396
+ class="remove-item"
1397
+ style="
1398
+ cursor: pointer;
1399
+ display: inline-block;
1400
+ padding: 3px 6px;
1401
+ border-right: 1px solid rgba(100,100,100,0.2);
1402
+ margin: 0;
1403
+ background: rgba(100,100,100,0.05);
1404
+ margin-top:1px;
1405
+ "
1406
+ @click=${(evt) => {
1407
+ evt.preventDefault();
1408
+ evt.stopPropagation();
1409
+ this.handleRemoveSelection(selected);
1410
+ }}
1411
+ >
1412
+ <temba-icon
1413
+ name="${Icon.delete_small}"
1414
+ size="1"
1415
+ ></temba-icon>
1416
+ </div>
1417
+ `
1418
+ : null}
1419
+ ${!this.input || this.multi
1420
+ ? this.renderSelectedItem(selected)
1421
+ : null}
1422
+ </div>
1423
+ `)}
1267
1424
  ${this.multi ? input : null}
1268
1425
  </div>
1269
1426
 
@@ -1509,4 +1666,7 @@ __decorate([
1509
1666
  __decorate([
1510
1667
  property({ type: Boolean })
1511
1668
  ], Select.prototype, "allowAnchor", void 0);
1669
+ __decorate([
1670
+ property({ type: String })
1671
+ ], Select.prototype, "draggingId", void 0);
1512
1672
  //# sourceMappingURL=Select.js.map