@nyaruka/temba-components 0.122.0 → 0.124.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 (262) hide show
  1. package/.github/copilot-instructions.md +181 -0
  2. package/.github/workflows/build.yml +3 -3
  3. package/.github/workflows/cla.yml +6 -6
  4. package/.github/workflows/copilot-setup-steps.yml +86 -0
  5. package/CHANGELOG.md +44 -0
  6. package/demo/drag-drop-demo.html +141 -0
  7. package/demo/index.html +57 -0
  8. package/demo/test-drag-drop.html +94 -0
  9. package/dist/locales/es.js +1 -0
  10. package/dist/locales/es.js.map +1 -1
  11. package/dist/locales/fr.js +1 -0
  12. package/dist/locales/fr.js.map +1 -1
  13. package/dist/locales/pt.js +1 -0
  14. package/dist/locales/pt.js.map +1 -1
  15. package/dist/temba-components.js +366 -247
  16. package/dist/temba-components.js.map +1 -1
  17. package/out-tsc/src/chart/TembaChart.js +81 -14
  18. package/out-tsc/src/chart/TembaChart.js.map +1 -1
  19. package/out-tsc/src/fields/FieldManager.js +27 -34
  20. package/out-tsc/src/fields/FieldManager.js.map +1 -1
  21. package/out-tsc/src/list/RunList.js +13 -8
  22. package/out-tsc/src/list/RunList.js.map +1 -1
  23. package/out-tsc/src/list/SortableList.js +257 -60
  24. package/out-tsc/src/list/SortableList.js.map +1 -1
  25. package/out-tsc/src/locales/es.js +1 -0
  26. package/out-tsc/src/locales/es.js.map +1 -1
  27. package/out-tsc/src/locales/fr.js +1 -0
  28. package/out-tsc/src/locales/fr.js.map +1 -1
  29. package/out-tsc/src/locales/pt.js +1 -0
  30. package/out-tsc/src/locales/pt.js.map +1 -1
  31. package/out-tsc/src/omnibox/Omnibox.js +1 -1
  32. package/out-tsc/src/omnibox/Omnibox.js.map +1 -1
  33. package/out-tsc/src/options/Options.js +36 -13
  34. package/out-tsc/src/options/Options.js.map +1 -1
  35. package/out-tsc/src/select/Select.js +226 -43
  36. package/out-tsc/src/select/Select.js.map +1 -1
  37. package/out-tsc/src/store/AppState.js +3 -3
  38. package/out-tsc/src/store/AppState.js.map +1 -1
  39. package/out-tsc/src/utils/index.js +6 -1
  40. package/out-tsc/src/utils/index.js.map +1 -1
  41. package/out-tsc/src/vectoricon/VectorIcon.js +2 -1
  42. package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
  43. package/out-tsc/src/webchat/WebChat.js +5 -2
  44. package/out-tsc/src/webchat/WebChat.js.map +1 -1
  45. package/out-tsc/temba-modules.js +0 -2
  46. package/out-tsc/temba-modules.js.map +1 -1
  47. package/out-tsc/test/temba-appstate-language.test.js +176 -0
  48. package/out-tsc/test/temba-appstate-language.test.js.map +1 -0
  49. package/out-tsc/test/temba-chart.test.js +125 -0
  50. package/out-tsc/test/temba-chart.test.js.map +1 -1
  51. package/out-tsc/test/temba-dropdown.test.js +317 -0
  52. package/out-tsc/test/temba-dropdown.test.js.map +1 -0
  53. package/out-tsc/test/temba-flow-editor-node.test.js +273 -0
  54. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -0
  55. package/out-tsc/test/temba-flow-editor.test.js +244 -0
  56. package/out-tsc/test/temba-flow-editor.test.js.map +1 -0
  57. package/out-tsc/test/temba-flow-plumber.test.js +145 -0
  58. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -0
  59. package/out-tsc/test/temba-flow-render.test.js +171 -0
  60. package/out-tsc/test/temba-flow-render.test.js.map +1 -0
  61. package/out-tsc/test/temba-omnibox.test.js +2 -3
  62. package/out-tsc/test/temba-omnibox.test.js.map +1 -1
  63. package/out-tsc/test/temba-run-list.test.js +588 -0
  64. package/out-tsc/test/temba-run-list.test.js.map +1 -0
  65. package/out-tsc/test/temba-select.test.js +149 -52
  66. package/out-tsc/test/temba-select.test.js.map +1 -1
  67. package/out-tsc/test/temba-sortable-list.test.js +91 -15
  68. package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
  69. package/out-tsc/test/temba-toast.test.js +299 -0
  70. package/out-tsc/test/temba-toast.test.js.map +1 -0
  71. package/out-tsc/test/temba-utils-index.test.js +1178 -0
  72. package/out-tsc/test/temba-utils-index.test.js.map +1 -0
  73. package/out-tsc/test/temba-webchat-lightbox-fix.test.js +42 -0
  74. package/out-tsc/test/temba-webchat-lightbox-fix.test.js.map +1 -0
  75. package/out-tsc/test/temba-webchat.test.js +816 -0
  76. package/out-tsc/test/temba-webchat.test.js.map +1 -0
  77. package/out-tsc/test/utils.test.js +33 -1
  78. package/out-tsc/test/utils.test.js.map +1 -1
  79. package/package.json +6 -8
  80. package/screenshots/truth/alert/error.png +0 -0
  81. package/screenshots/truth/alert/info.png +0 -0
  82. package/screenshots/truth/alert/warning.png +0 -0
  83. package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
  84. package/screenshots/truth/checkbox/checked.png +0 -0
  85. package/screenshots/truth/checkbox/default.png +0 -0
  86. package/screenshots/truth/colorpicker/default.png +0 -0
  87. package/screenshots/truth/colorpicker/focused.png +0 -0
  88. package/screenshots/truth/colorpicker/initialized.png +0 -0
  89. package/screenshots/truth/colorpicker/selected.png +0 -0
  90. package/screenshots/truth/compose/attachments-tab.png +0 -0
  91. package/screenshots/truth/compose/attachments-with-files-focused.png +0 -0
  92. package/screenshots/truth/compose/attachments-with-files.png +0 -0
  93. package/screenshots/truth/compose/intial-text.png +0 -0
  94. package/screenshots/truth/compose/no-counter.png +0 -0
  95. package/screenshots/truth/compose/wraps-text-and-spaces.png +0 -0
  96. package/screenshots/truth/compose/wraps-text-and-url.png +0 -0
  97. package/screenshots/truth/compose/wraps-text-no-spaces.png +0 -0
  98. package/screenshots/truth/contacts/badges.png +0 -0
  99. package/screenshots/truth/contacts/chat-failure.png +0 -0
  100. package/screenshots/truth/contacts/chat-for-active-contact.png +0 -0
  101. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  102. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  103. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  104. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  105. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  106. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  107. package/screenshots/truth/content-menu/button-no-items.png +0 -0
  108. package/screenshots/truth/content-menu/items-and-buttons.png +0 -0
  109. package/screenshots/truth/counter/summary.png +0 -0
  110. package/screenshots/truth/counter/text.png +0 -0
  111. package/screenshots/truth/counter/unicode-variables.png +0 -0
  112. package/screenshots/truth/counter/unicode.png +0 -0
  113. package/screenshots/truth/counter/variable.png +0 -0
  114. package/screenshots/truth/date/date-inline.png +0 -0
  115. package/screenshots/truth/date/date.png +0 -0
  116. package/screenshots/truth/date/datetime.png +0 -0
  117. package/screenshots/truth/date/duration.png +0 -0
  118. package/screenshots/truth/date/timedate.png +0 -0
  119. package/screenshots/truth/datepicker/date-truncated-time.png +0 -0
  120. package/screenshots/truth/datepicker/date.png +0 -0
  121. package/screenshots/truth/datepicker/initial-timezone.png +0 -0
  122. package/screenshots/truth/datepicker/updated-keyboard-date.png +0 -0
  123. package/screenshots/truth/dialog/focused.png +0 -0
  124. package/screenshots/truth/dropdown/after-blur.png +0 -0
  125. package/screenshots/truth/dropdown/bottom-edge-collision.png +0 -0
  126. package/screenshots/truth/dropdown/custom-arrow-size.png +0 -0
  127. package/screenshots/truth/dropdown/default.png +0 -0
  128. package/screenshots/truth/dropdown/narrow-toggle.png +0 -0
  129. package/screenshots/truth/dropdown/no-mask.png +0 -0
  130. package/screenshots/truth/dropdown/opened.png +0 -0
  131. package/screenshots/truth/dropdown/positioned.png +0 -0
  132. package/screenshots/truth/dropdown/right-edge-collision.png +0 -0
  133. package/screenshots/truth/dropdown/with-mask.png +0 -0
  134. package/screenshots/truth/flow/editor-basic.png +0 -0
  135. package/screenshots/truth/label/custom.png +0 -0
  136. package/screenshots/truth/label/danger.png +0 -0
  137. package/screenshots/truth/label/dark.png +0 -0
  138. package/screenshots/truth/label/default-icon.png +0 -0
  139. package/screenshots/truth/label/no-icon.png +0 -0
  140. package/screenshots/truth/label/primary.png +0 -0
  141. package/screenshots/truth/label/secondary.png +0 -0
  142. package/screenshots/truth/label/shadow.png +0 -0
  143. package/screenshots/truth/label/tertiary.png +0 -0
  144. package/screenshots/truth/lightbox/img-zoomed.png +0 -0
  145. package/screenshots/truth/list/fields-dragging.png +0 -0
  146. package/screenshots/truth/list/fields-filtered.png +0 -0
  147. package/screenshots/truth/list/fields-hovered.png +0 -0
  148. package/screenshots/truth/list/fields.png +0 -0
  149. package/screenshots/truth/list/items-selected.png +0 -0
  150. package/screenshots/truth/list/items-updated.png +0 -0
  151. package/screenshots/truth/list/items.png +0 -0
  152. package/screenshots/truth/list/sortable-dragging.png +0 -0
  153. package/screenshots/truth/list/sortable-dropped.png +0 -0
  154. package/screenshots/truth/list/sortable.png +0 -0
  155. package/screenshots/truth/menu/menu-focused-with items.png +0 -0
  156. package/screenshots/truth/menu/menu-refresh-1.png +0 -0
  157. package/screenshots/truth/menu/menu-refresh-2.png +0 -0
  158. package/screenshots/truth/menu/menu-root.png +0 -0
  159. package/screenshots/truth/menu/menu-submenu.png +0 -0
  160. package/screenshots/truth/menu/menu-tasks-nextup.png +0 -0
  161. package/screenshots/truth/menu/menu-tasks.png +0 -0
  162. package/screenshots/truth/modax/form.png +0 -0
  163. package/screenshots/truth/modax/simple.png +0 -0
  164. package/screenshots/truth/omnibox/selected.png +0 -0
  165. package/screenshots/truth/options/block.png +0 -0
  166. package/screenshots/truth/run-list/basic.png +0 -0
  167. package/screenshots/truth/select/disabled-multi-selection.png +0 -0
  168. package/screenshots/truth/select/disabled-selection.png +0 -0
  169. package/screenshots/truth/select/disabled.png +0 -0
  170. package/screenshots/truth/select/embedded.png +0 -0
  171. package/screenshots/truth/select/empty-options.png +0 -0
  172. package/screenshots/truth/select/expression-selected.png +0 -0
  173. package/screenshots/truth/select/expressions.png +0 -0
  174. package/screenshots/truth/select/functions.png +0 -0
  175. package/screenshots/truth/select/local-options.png +0 -0
  176. package/screenshots/truth/select/multi-reorder-final.png +0 -0
  177. package/screenshots/truth/select/multi-reorder-initial.png +0 -0
  178. package/screenshots/truth/select/multi-with-endpoint.png +0 -0
  179. package/screenshots/truth/select/multiple-initial-values.png +0 -0
  180. package/screenshots/truth/select/remote-options.png +0 -0
  181. package/screenshots/truth/select/search-enabled.png +0 -0
  182. package/screenshots/truth/select/search-multi-no-matches.png +0 -0
  183. package/screenshots/truth/select/search-selected-focus.png +0 -0
  184. package/screenshots/truth/select/search-selected.png +0 -0
  185. package/screenshots/truth/select/search-with-selected.png +0 -0
  186. package/screenshots/truth/select/searching.png +0 -0
  187. package/screenshots/truth/select/selected-multi-maxitems-reached.png +0 -0
  188. package/screenshots/truth/select/selected-multi.png +0 -0
  189. package/screenshots/truth/select/selected-single.png +0 -0
  190. package/screenshots/truth/select/selection-clearable.png +0 -0
  191. package/screenshots/truth/select/static-initial-value.png +0 -0
  192. package/screenshots/truth/select/static-initial-via-selected.png +0 -0
  193. package/screenshots/truth/select/truncated-selection.png +0 -0
  194. package/screenshots/truth/select/with-placeholder.png +0 -0
  195. package/screenshots/truth/select/without-placeholder.png +0 -0
  196. package/screenshots/truth/slider/custom-min-custom-max-valid-value.png +0 -0
  197. package/screenshots/truth/slider/custom-min-default-max-no-value.png +0 -0
  198. package/screenshots/truth/slider/default-min-custom-max-no-value.png +0 -0
  199. package/screenshots/truth/slider/default-min-default-max-invalid-value.png +0 -0
  200. package/screenshots/truth/slider/default-min-default-max-valid-value.png +0 -0
  201. package/screenshots/truth/slider/update-slider-on-value-change.png +0 -0
  202. package/screenshots/truth/templates/default.png +0 -0
  203. package/screenshots/truth/templates/unapproved.png +0 -0
  204. package/screenshots/truth/textinput/input-disabled.png +0 -0
  205. package/screenshots/truth/textinput/input-focused.png +0 -0
  206. package/screenshots/truth/textinput/input-form.png +0 -0
  207. package/screenshots/truth/textinput/input-inserted.png +0 -0
  208. package/screenshots/truth/textinput/input-placeholder.png +0 -0
  209. package/screenshots/truth/textinput/input-updated.png +0 -0
  210. package/screenshots/truth/textinput/input.png +0 -0
  211. package/screenshots/truth/textinput/textarea-focused.png +0 -0
  212. package/screenshots/truth/textinput/textarea.png +0 -0
  213. package/screenshots/truth/tip/bottom.png +0 -0
  214. package/screenshots/truth/tip/left.png +0 -0
  215. package/screenshots/truth/tip/right.png +0 -0
  216. package/screenshots/truth/tip/top.png +0 -0
  217. package/screenshots/truth/webchat/closed-widget.png +0 -0
  218. package/screenshots/truth/webchat/connected-state.png +0 -0
  219. package/screenshots/truth/webchat/connecting-state.png +0 -0
  220. package/screenshots/truth/webchat/disconnected-state.png +0 -0
  221. package/screenshots/truth/webchat/opened-widget.png +0 -0
  222. package/src/chart/TembaChart.ts +86 -15
  223. package/src/fields/FieldManager.ts +30 -38
  224. package/src/list/RunList.ts +11 -8
  225. package/src/list/SortableList.ts +291 -67
  226. package/src/locales/es.ts +1 -0
  227. package/src/locales/fr.ts +1 -0
  228. package/src/locales/pt.ts +1 -0
  229. package/src/omnibox/Omnibox.ts +1 -1
  230. package/src/options/Options.ts +38 -13
  231. package/src/select/Select.ts +245 -47
  232. package/src/store/AppState.ts +3 -3
  233. package/src/utils/index.ts +17 -5
  234. package/src/vectoricon/VectorIcon.ts +2 -1
  235. package/src/webchat/WebChat.ts +5 -2
  236. package/temba-modules.ts +0 -2
  237. package/test/temba-appstate-language.test.ts +218 -0
  238. package/test/temba-chart.test.ts +161 -1
  239. package/test/temba-dropdown.test.ts +444 -0
  240. package/test/temba-flow-editor-node.test.ts +344 -0
  241. package/test/temba-flow-editor.test.ts +301 -0
  242. package/test/temba-flow-plumber.test.ts +189 -0
  243. package/test/temba-flow-render.test.ts +220 -0
  244. package/test/temba-omnibox.test.ts +2 -3
  245. package/test/temba-run-list.test.ts +774 -0
  246. package/test/temba-select.test.ts +206 -78
  247. package/test/temba-sortable-list.test.ts +108 -15
  248. package/test/temba-toast.test.ts +386 -0
  249. package/test/temba-utils-index.test.ts +1547 -0
  250. package/test/temba-webchat-lightbox-fix.test.ts +57 -0
  251. package/test/temba-webchat.test.ts +1095 -0
  252. package/test/utils.test.ts +56 -2
  253. package/test-assets/list/flow-results.json +17 -0
  254. package/test-assets/list/runs.json +126 -0
  255. package/test-assets/style.css +23 -0
  256. package/web-test-runner.config.mjs +33 -7
  257. package/xliff/es.xlf +3 -0
  258. package/xliff/fr.xlf +3 -0
  259. package/xliff/pt.xlf +3 -0
  260. package/out-tsc/src/outboxmonitor/OutboxMonitor.js +0 -136
  261. package/out-tsc/src/outboxmonitor/OutboxMonitor.js.map +0 -1
  262. package/src/outboxmonitor/OutboxMonitor.ts +0 -148
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-empty-function */
2
2
  import { TemplateResult, html, css, CSSResult, CSSResultArray } from 'lit';
3
- import { property } from 'lit/decorators.js';
3
+ import { property, state } from 'lit/decorators.js';
4
4
  import {
5
5
  getUrl,
6
6
  getClasses,
@@ -9,6 +9,7 @@ import {
9
9
  postJSON
10
10
  } from '../utils';
11
11
  import '../options/Options';
12
+ import '../list/SortableList';
12
13
  import { EventHandler } from '../RapidElement';
13
14
  import { FormElement } from '../FormElement';
14
15
 
@@ -76,7 +77,7 @@ export class Select<T extends SelectOption> extends FormElement {
76
77
  background: rgba(100, 100, 100, 0.05);
77
78
  }
78
79
 
79
- .selected-item.multi .remove-item {
80
+ . selected-item.multi .remove-item {
80
81
  display: none;
81
82
  }
82
83
 
@@ -112,7 +113,7 @@ export class Select<T extends SelectOption> extends FormElement {
112
113
  padding-top: 1px;
113
114
  box-shadow: var(--widget-box-shadow);
114
115
  position: relative;
115
- min-height: var(--temba-select-min-height, 2.5em);
116
+ min-height: var(--temba-select-min-height, 2.4em);
116
117
  }
117
118
 
118
119
  temba-icon.select-open:hover,
@@ -148,7 +149,7 @@ export class Select<T extends SelectOption> extends FormElement {
148
149
  flex-direction: row;
149
150
  align-items: stretch;
150
151
  user-select: none;
151
- padding: var(--temba-select-selected-padding);
152
+ padding: var(--temba-select-selected-padding, 0px 4px);
152
153
  }
153
154
 
154
155
  .searchable .selected {
@@ -207,6 +208,18 @@ export class Select<T extends SelectOption> extends FormElement {
207
208
  background: rgba(100, 100, 100, 0.3);
208
209
  }
209
210
 
211
+ .multi .selected-item.sortable {
212
+ cursor: move;
213
+ }
214
+
215
+ .multi .selected-item.dragging {
216
+ opacity: 0.5;
217
+ }
218
+
219
+ .multi temba-sortable-list {
220
+ margin: 0 !important;
221
+ }
222
+
210
223
  input {
211
224
  font-size: 13px;
212
225
  width: 0px;
@@ -225,16 +238,15 @@ export class Select<T extends SelectOption> extends FormElement {
225
238
  border: 0px solid purple !important;
226
239
  }
227
240
 
228
- .input-wrapper {
229
- min-width: 1px;
230
- }
231
-
232
241
  .input-wrapper:focus-within {
233
242
  min-width: 1px;
234
243
  }
235
244
 
236
245
  .input-wrapper {
246
+ min-width: 1px;
237
247
  margin-left: 6px;
248
+ margin-right: -6px;
249
+ display: flex;
238
250
  }
239
251
 
240
252
  .multi .input-wrapper {
@@ -274,9 +286,9 @@ export class Select<T extends SelectOption> extends FormElement {
274
286
  box-shadow: none !important;
275
287
  }
276
288
 
277
- .input-wrapper {
278
- display: flex;
279
- margin-right: 0em;
289
+ .multi .input-wrapper {
290
+ flex-shrink: 0;
291
+ min-width: 100px;
280
292
  }
281
293
 
282
294
  .input-wrapper .searchbox {
@@ -291,6 +303,7 @@ export class Select<T extends SelectOption> extends FormElement {
291
303
  color: var(--color-placeholder);
292
304
  display: none;
293
305
  line-height: var(--temba-select-selected-line-height);
306
+ margin-left: 6px;
294
307
  }
295
308
 
296
309
  .footer {
@@ -332,6 +345,10 @@ export class Select<T extends SelectOption> extends FormElement {
332
345
  pointer-events: none;
333
346
  padding: 0px;
334
347
  }
348
+
349
+ .ghost .remove-item {
350
+ display: none !important;
351
+ }
335
352
  `;
336
353
  }
337
354
 
@@ -410,6 +427,9 @@ export class Select<T extends SelectOption> extends FormElement {
410
427
  @property({ type: Boolean })
411
428
  disabled = false;
412
429
 
430
+ @state()
431
+ attemptedOpen = false;
432
+
413
433
  @property({ attribute: false })
414
434
  selectedIndex = -1;
415
435
 
@@ -512,6 +532,9 @@ export class Select<T extends SelectOption> extends FormElement {
512
532
  @property({ type: Boolean })
513
533
  allowAnchor: boolean = true;
514
534
 
535
+ @property({ type: String })
536
+ draggingId: string;
537
+
515
538
  private alphaSort = (a: any, b: any) => {
516
539
  // by default, all endpoint values are sorted by name
517
540
  if (this.endpoint) {
@@ -535,6 +558,7 @@ export class Select<T extends SelectOption> extends FormElement {
535
558
  this.renderSelectedItemDefault = this.renderSelectedItemDefault.bind(this);
536
559
  this.prepareOptionsDefault = this.prepareOptionsDefault.bind(this);
537
560
  this.isMatchDefault = this.isMatchDefault.bind(this);
561
+ this.handleOrderChanged = this.handleOrderChanged.bind(this);
538
562
  }
539
563
 
540
564
  public prepareOptionsDefault(options: T[]): T[] {
@@ -863,6 +887,7 @@ export class Select<T extends SelectOption> extends FormElement {
863
887
  }
864
888
 
865
889
  this.visibleOptions = [];
890
+ this.attemptedOpen = false;
866
891
  this.input = '';
867
892
  this.next = null;
868
893
  this.complete = true;
@@ -955,7 +980,9 @@ export class Select<T extends SelectOption> extends FormElement {
955
980
  }
956
981
 
957
982
  public isOpen(): boolean {
958
- return this.visibleOptions.length > 0;
983
+ return (
984
+ this.visibleOptions.length > 0 || (this.attemptedOpen && this.focused)
985
+ );
959
986
  }
960
987
 
961
988
  public setOptions(options: any[]): void {
@@ -1212,6 +1239,7 @@ export class Select<T extends SelectOption> extends FormElement {
1212
1239
 
1213
1240
  private handleBlur() {
1214
1241
  this.focused = false;
1242
+ this.attemptedOpen = false;
1215
1243
  if (this.visibleOptions.length > 0) {
1216
1244
  this.input = '';
1217
1245
  this.next = null;
@@ -1285,8 +1313,10 @@ export class Select<T extends SelectOption> extends FormElement {
1285
1313
  ) {
1286
1314
  if (
1287
1315
  this.visibleOptions.length === 0 &&
1288
- this.completionOptions.length === 0
1316
+ this.completionOptions.length === 0 &&
1317
+ !this.input
1289
1318
  ) {
1319
+ this.attemptedOpen = true;
1290
1320
  this.requestUpdate('input');
1291
1321
  return;
1292
1322
  }
@@ -1322,6 +1352,7 @@ export class Select<T extends SelectOption> extends FormElement {
1322
1352
 
1323
1353
  private handleCancel() {
1324
1354
  this.visibleOptions = [];
1355
+ this.attemptedOpen = false;
1325
1356
  }
1326
1357
 
1327
1358
  private handleCursorChanged(event: CustomEvent) {
@@ -1329,8 +1360,13 @@ export class Select<T extends SelectOption> extends FormElement {
1329
1360
  }
1330
1361
 
1331
1362
  private handleContainerClick(event: MouseEvent) {
1332
- event.stopPropagation();
1333
- event.preventDefault();
1363
+ if (this.disabled) {
1364
+ // prevent opening dropdown right after drag-and-drop
1365
+ event.stopPropagation();
1366
+ event.preventDefault();
1367
+ return;
1368
+ }
1369
+
1334
1370
  this.focused = true;
1335
1371
  if ((event.target as any).tagName !== 'INPUT') {
1336
1372
  const input = this.shadowRoot.querySelector('input');
@@ -1340,10 +1376,15 @@ export class Select<T extends SelectOption> extends FormElement {
1340
1376
  return;
1341
1377
  }
1342
1378
 
1343
- if (this.visibleOptions.length > 0) {
1379
+ // Check if dropdown is currently open (either with options or showing "No options")
1380
+ if (this.isOpen()) {
1344
1381
  this.visibleOptions = [];
1382
+ this.attemptedOpen = false;
1345
1383
  } else {
1384
+ this.attemptedOpen = true;
1346
1385
  this.requestUpdate('input');
1386
+ // Also trigger an immediate update to show empty dropdown
1387
+ this.requestUpdate();
1347
1388
  }
1348
1389
  }
1349
1390
  }
@@ -1361,6 +1402,9 @@ export class Select<T extends SelectOption> extends FormElement {
1361
1402
  }
1362
1403
 
1363
1404
  private handleArrowClick(event: MouseEvent): void {
1405
+ if (this.disabled) {
1406
+ return;
1407
+ }
1364
1408
  if (this.isOpen()) {
1365
1409
  event.preventDefault();
1366
1410
  event.stopPropagation();
@@ -1376,7 +1420,16 @@ export class Select<T extends SelectOption> extends FormElement {
1376
1420
  // special case for icons on any option type
1377
1421
  const icon = (option as any).icon;
1378
1422
  return html`
1379
- <div class="option-name" style="display:flex">
1423
+ <div
1424
+ class="option-name"
1425
+ style="flex: 1 1 auto;
1426
+ align-self: center;
1427
+ white-space: nowrap;
1428
+ overflow: hidden;
1429
+ text-overflow: ellipsis;
1430
+ padding: 2px 8px;
1431
+ display: flex;"
1432
+ >
1380
1433
  ${icon
1381
1434
  ? html`<temba-icon
1382
1435
  name="${icon}"
@@ -1443,6 +1496,18 @@ export class Select<T extends SelectOption> extends FormElement {
1443
1496
  const idx = this.values.indexOf(valueToRemove);
1444
1497
  if (idx > -1) {
1445
1498
  this.values.splice(idx, 1);
1499
+
1500
+ // Also remove the 'selected' attribute from the corresponding temba-option element
1501
+ const valueToMatch = this.getValue(valueToRemove);
1502
+ for (const child of this.children) {
1503
+ if (child.tagName === 'TEMBA-OPTION') {
1504
+ const childValue = child.getAttribute('value');
1505
+ if (childValue === valueToMatch) {
1506
+ child.removeAttribute('selected');
1507
+ break;
1508
+ }
1509
+ }
1510
+ }
1446
1511
  }
1447
1512
  this.requestUpdate('values', oldValues);
1448
1513
  this.infoText = '';
@@ -1461,6 +1526,41 @@ export class Select<T extends SelectOption> extends FormElement {
1461
1526
  this.requestUpdate('values', oldValues);
1462
1527
  }
1463
1528
 
1529
+ private shouldShowEmptyMessage(): boolean {
1530
+ return (
1531
+ this.attemptedOpen &&
1532
+ this.focused &&
1533
+ this.visibleOptions.length === 0 &&
1534
+ !this.input &&
1535
+ this.staticOptions.length === 0 &&
1536
+ !this.endpoint
1537
+ );
1538
+ }
1539
+
1540
+ private handleOrderChanged(event: CustomEvent): void {
1541
+ const detail = event.detail;
1542
+
1543
+ // Handle new swap-based format
1544
+ if (detail.swap && Array.isArray(detail.swap) && detail.swap.length === 2) {
1545
+ const [fromIdx, toIdx] = detail.swap;
1546
+
1547
+ // Only reorder if the indexes are different and valid
1548
+ if (
1549
+ fromIdx !== toIdx &&
1550
+ fromIdx >= 0 &&
1551
+ toIdx >= 0 &&
1552
+ fromIdx < this.values.length &&
1553
+ toIdx < this.values.length
1554
+ ) {
1555
+ const oldValues = [...this.values];
1556
+ // Move the item from fromIdx to toIdx
1557
+ const movedItem = this.values.splice(fromIdx, 1)[0];
1558
+ this.values.splice(toIdx, 0, movedItem);
1559
+ this.requestUpdate('values', oldValues);
1560
+ }
1561
+ }
1562
+ }
1563
+
1464
1564
  public render(): TemplateResult {
1465
1565
  const placeholder = this.values.length === 0 ? this.placeholder : '';
1466
1566
  const placeholderDiv = html`
@@ -1545,35 +1645,132 @@ export class Select<T extends SelectOption> extends FormElement {
1545
1645
  : null
1546
1646
  }
1547
1647
  ${!this.multi && !this.resolving ? input : null}
1548
- ${this.values.map(
1549
- (selected: any, index: number) => html`
1550
- <div
1551
- class="selected-item ${index === this.selectedIndex
1552
- ? 'focused'
1553
- : ''}"
1554
- >
1555
- ${this.multi
1556
- ? html`
1557
- <div
1558
- class="remove-item"
1559
- style="margin-top:1px"
1560
- @click=${(evt: MouseEvent) => {
1561
- evt.preventDefault();
1562
- evt.stopPropagation();
1563
- this.handleRemoveSelection(selected);
1564
- }}
1565
- >
1566
- <temba-icon
1567
- name="${Icon.delete_small}"
1568
- size="1"
1569
- ></temba-icon>
1570
- </div>
1571
- `
1572
- : null}
1573
- ${this.renderSelectedItem(selected)}
1574
- </div>
1575
- `
1576
- )}
1648
+ ${
1649
+ this.multi && this.values.length > 1
1650
+ ? html`
1651
+ <temba-sortable-list
1652
+ horizontal
1653
+ @temba-order-changed=${this.handleOrderChanged}
1654
+ .prepareGhost=${(item: any) => {
1655
+ item.style.transform = 'scale(1)';
1656
+ item.querySelector('.remove-item').style.display =
1657
+ 'none';
1658
+ }}
1659
+ >
1660
+ ${this.values.map(
1661
+ (selected: any, index: number) => html`
1662
+ <div
1663
+ class="selected-item sortable ${index ===
1664
+ this.selectedIndex
1665
+ ? 'focused'
1666
+ : ''} ${this.draggingId === `selected-${index}`
1667
+ ? 'dragging'
1668
+ : ''}"
1669
+ id="selected-${index}"
1670
+ style="
1671
+ vertical-align: middle;
1672
+ background: rgba(100,100,100,0.1);
1673
+ user-select: none;
1674
+ border-radius: 2px;
1675
+ align-items: center;
1676
+ flex-direction: row;
1677
+ flex-wrap: nowrap;
1678
+ margin: 2px 2px;
1679
+ display: flex;
1680
+ overflow: hidden;
1681
+ color: var(--color-widget-text);
1682
+ line-height: var(--temba-select-selected-line-height);
1683
+ --icon-color: var(--color-text-dark);
1684
+ ${index === this.selectedIndex
1685
+ ? 'background: rgba(100,100,100,0.3);'
1686
+ : ''}
1687
+ ${this.draggingId === `selected-${index}`
1688
+ ? 'opacity: 0.5;'
1689
+ : ''}
1690
+ "
1691
+ >
1692
+ ${this.multi
1693
+ ? html`
1694
+ <div
1695
+ class="remove-item"
1696
+ style="
1697
+ cursor: pointer;
1698
+ display: inline-block;
1699
+ padding: 3px 6px;
1700
+ border-right: 1px solid rgba(100,100,100,0.2);
1701
+ margin: 0;
1702
+ background: rgba(100,100,100,0.05);
1703
+ margin-top:1px;
1704
+ "
1705
+ @click=${(evt: MouseEvent) => {
1706
+ evt.preventDefault();
1707
+ evt.stopPropagation();
1708
+ this.handleRemoveSelection(selected);
1709
+ }}
1710
+ >
1711
+ <temba-icon
1712
+ name="${Icon.delete_small}"
1713
+ size="1"
1714
+ ></temba-icon>
1715
+ </div>
1716
+ `
1717
+ : null}
1718
+ ${this.renderSelectedItem(selected)}
1719
+ </div>
1720
+ `
1721
+ )}
1722
+ </temba-sortable-list>
1723
+ `
1724
+ : this.values.map(
1725
+ (selected: any, index: number) => html`
1726
+ <div
1727
+ class="selected-item ${index === this.selectedIndex
1728
+ ? 'focused'
1729
+ : ''}"
1730
+ style="
1731
+ display: flex;
1732
+ overflow: hidden;
1733
+ color: var(--color-widget-text);
1734
+ line-height: var(--temba-select-selected-line-height);
1735
+ --icon-color: var(--color-text-dark);
1736
+ ${index === this.selectedIndex
1737
+ ? 'background: rgba(100,100,100,0.3);'
1738
+ : ''}
1739
+ "
1740
+ >
1741
+ ${this.multi
1742
+ ? html`
1743
+ <div
1744
+ class="remove-item"
1745
+ style="
1746
+ cursor: pointer;
1747
+ display: inline-block;
1748
+ padding: 3px 6px;
1749
+ border-right: 1px solid rgba(100,100,100,0.2);
1750
+ margin: 0;
1751
+ background: rgba(100,100,100,0.05);
1752
+ margin-top:1px;
1753
+ "
1754
+ @click=${(evt: MouseEvent) => {
1755
+ evt.preventDefault();
1756
+ evt.stopPropagation();
1757
+ this.handleRemoveSelection(selected);
1758
+ }}
1759
+ >
1760
+ <temba-icon
1761
+ name="${Icon.delete_small}"
1762
+ size="1"
1763
+ ></temba-icon>
1764
+ </div>
1765
+ `
1766
+ : null}
1767
+ ${!this.input
1768
+ ? this.renderSelectedItem(selected)
1769
+ : null}
1770
+ </div>
1771
+ `
1772
+ )
1773
+ }
1577
1774
  ${this.multi ? input : null}
1578
1775
  </div>
1579
1776
 
@@ -1620,7 +1817,8 @@ export class Select<T extends SelectOption> extends FormElement {
1620
1817
  .getName=${this.getNameInternal}
1621
1818
  ?static-width=${this.optionWidth}
1622
1819
  ?anchor-right=${this.anchorRight}
1623
- ?visible=${this.visibleOptions.length > 0}
1820
+ ?visible=${this.visibleOptions.length > 0 || this.shouldShowEmptyMessage()}
1821
+ ?showEmptyMessage=${this.shouldShowEmptyMessage()}
1624
1822
  ></temba-options>
1625
1823
 
1626
1824
  <temba-options
@@ -157,11 +157,11 @@ export const zustand = createStore<AppState>()(
157
157
  setFlowContents: (flow: FlowContents) => {
158
158
  set((state: AppState) => {
159
159
  const flowLang = flow.definition.language;
160
- const languageCode = state.languageCode || flowLang;
161
160
  state.flowDefinition = flow.definition;
162
161
  state.flowInfo = flow.info;
163
- state.isTranslating = flowLang !== languageCode;
164
- state.languageCode = languageCode || flowLang;
162
+ // Reset to the flow's default language when loading a new flow
163
+ state.languageCode = flowLang;
164
+ state.isTranslating = false;
165
165
  });
166
166
  },
167
167
 
@@ -494,7 +494,7 @@ export const getScrollParent = (node: any): any => {
494
494
  return null;
495
495
  };
496
496
 
497
- export const isElementVisible = (el: any, holder: any) => {
497
+ export const isElementVisible = (el: any, holder?: any) => {
498
498
  holder = holder || document.body;
499
499
  const { top, bottom } = el.getBoundingClientRect();
500
500
  const holderRect = holder.getBoundingClientRect();
@@ -575,7 +575,7 @@ export const timeSince = (
575
575
  }
576
576
  };
577
577
 
578
- export const isDate = (value: string): boolean => {
578
+ export const isDate = (value: any): boolean => {
579
579
  if (toString.call(value) === '[object Date]') {
580
580
  return true;
581
581
  }
@@ -635,6 +635,10 @@ export const truncate = (input: string, max: number): string => {
635
635
  };
636
636
 
637
637
  export const oxford = (items: any[], joiner = 'and'): any => {
638
+ if (items.length === 0) {
639
+ return '';
640
+ }
641
+
638
642
  if (items.length === 1) {
639
643
  return items[0];
640
644
  }
@@ -657,7 +661,9 @@ export const oxford = (items: any[], joiner = 'and'): any => {
657
661
  });
658
662
  }
659
663
 
660
- return items.join(', ') + joiner + items[items.length - 1];
664
+ const allButLast = items.slice(0, -1);
665
+ const last = items[items.length - 1];
666
+ return allButLast.join(', ') + ', ' + joiner + ' ' + last;
661
667
  };
662
668
 
663
669
  export const oxfordFn = (
@@ -713,13 +719,19 @@ export enum COOKIE_KEYS {
713
719
  TICKET_SHOW_DETAILS = 'tickets.show-details'
714
720
  }
715
721
 
716
- export const capitalize = ([first, ...rest], locale = navigator.language) =>
722
+ export const capitalize = (
723
+ [first, ...rest]: string[] | string,
724
+ locale = navigator.language
725
+ ) =>
717
726
  first === undefined ? '' : first.toLocaleUpperCase(locale) + rest.join('');
718
727
 
719
728
  export const formatFileType = (type: string): string => {
720
729
  return type.split('/')[1];
721
730
  };
722
- export const formatFileSize = (bytes: number, decimalPoint: number): string => {
731
+ export const formatFileSize = (
732
+ bytes: number,
733
+ decimalPoint?: number
734
+ ): string => {
723
735
  if (bytes == 0) return '0 KB';
724
736
  const k = 1024,
725
737
  dm = decimalPoint || 2,
@@ -127,9 +127,10 @@ export class VectorIcon extends LitElement {
127
127
 
128
128
  .spin-forever {
129
129
  animation-name: spin;
130
- animation-duration: 2000ms;
130
+ animation-duration: var(--test-animation-duration, 2000ms);
131
131
  animation-iteration-count: infinite;
132
132
  animation-timing-function: linear;
133
+ animation-play-state: var(--test-animation-play-state, running);
133
134
  }
134
135
 
135
136
  @keyframes spin {
@@ -398,8 +398,11 @@ export class WebChat extends LitElement {
398
398
  super.firstUpdated(changed);
399
399
  this.chat = this.shadowRoot.querySelector('temba-chat');
400
400
 
401
- const lightbox = document.createElement('temba-lightbox');
402
- document.querySelector('body').appendChild(lightbox);
401
+ // Only create lightbox if one doesn't already exist
402
+ if (!document.querySelector('temba-lightbox')) {
403
+ const lightbox = document.createElement('temba-lightbox');
404
+ document.querySelector('body').appendChild(lightbox);
405
+ }
403
406
  }
404
407
 
405
408
  private handleReconnect() {
package/temba-modules.ts CHANGED
@@ -58,7 +58,6 @@ import { MediaPicker } from './src/mediapicker/MediaPicker';
58
58
  import { Editor } from './src/flow/Editor';
59
59
  import { EditorNode } from './src/flow/EditorNode';
60
60
  import { ContactNotepad } from './src/contacts/ContactNotepad';
61
- import { OutboxMonitor } from './src/outboxmonitor/OutboxMonitor';
62
61
  import { ProgressBar } from './src/progress/ProgressBar';
63
62
  import { StartProgress } from './src/progress/StartProgress';
64
63
  import { ShortcutList } from './src/list/ShortcutList';
@@ -134,7 +133,6 @@ addCustomElement('temba-media-picker', MediaPicker);
134
133
  addCustomElement('temba-flow-editor', Editor);
135
134
  addCustomElement('temba-flow-node', EditorNode);
136
135
  addCustomElement('temba-contact-notepad', ContactNotepad);
137
- addCustomElement('temba-outbox-monitor', OutboxMonitor);
138
136
  addCustomElement('temba-progress', ProgressBar);
139
137
  addCustomElement('temba-start-progress', StartProgress);
140
138
  addCustomElement('temba-shortcuts', ShortcutList);