@nyaruka/temba-components 0.122.0 → 0.123.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 (224) hide show
  1. package/.github/copilot-instructions.md +163 -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 +33 -0
  6. package/demo/index.html +42 -0
  7. package/dist/locales/es.js +1 -0
  8. package/dist/locales/es.js.map +1 -1
  9. package/dist/locales/fr.js +1 -0
  10. package/dist/locales/fr.js.map +1 -1
  11. package/dist/locales/pt.js +1 -0
  12. package/dist/locales/pt.js.map +1 -1
  13. package/dist/temba-components.js +59 -72
  14. package/dist/temba-components.js.map +1 -1
  15. package/out-tsc/src/chart/TembaChart.js +81 -14
  16. package/out-tsc/src/chart/TembaChart.js.map +1 -1
  17. package/out-tsc/src/list/RunList.js +13 -8
  18. package/out-tsc/src/list/RunList.js.map +1 -1
  19. package/out-tsc/src/locales/es.js +1 -0
  20. package/out-tsc/src/locales/es.js.map +1 -1
  21. package/out-tsc/src/locales/fr.js +1 -0
  22. package/out-tsc/src/locales/fr.js.map +1 -1
  23. package/out-tsc/src/locales/pt.js +1 -0
  24. package/out-tsc/src/locales/pt.js.map +1 -1
  25. package/out-tsc/src/options/Options.js +36 -13
  26. package/out-tsc/src/options/Options.js.map +1 -1
  27. package/out-tsc/src/select/Select.js +28 -5
  28. package/out-tsc/src/select/Select.js.map +1 -1
  29. package/out-tsc/src/store/AppState.js +3 -3
  30. package/out-tsc/src/store/AppState.js.map +1 -1
  31. package/out-tsc/src/utils/index.js +6 -1
  32. package/out-tsc/src/utils/index.js.map +1 -1
  33. package/out-tsc/src/vectoricon/VectorIcon.js +2 -1
  34. package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
  35. package/out-tsc/temba-modules.js +0 -2
  36. package/out-tsc/temba-modules.js.map +1 -1
  37. package/out-tsc/test/temba-appstate-language.test.js +176 -0
  38. package/out-tsc/test/temba-appstate-language.test.js.map +1 -0
  39. package/out-tsc/test/temba-chart.test.js +125 -0
  40. package/out-tsc/test/temba-chart.test.js.map +1 -1
  41. package/out-tsc/test/temba-dropdown.test.js +317 -0
  42. package/out-tsc/test/temba-dropdown.test.js.map +1 -0
  43. package/out-tsc/test/temba-run-list.test.js +588 -0
  44. package/out-tsc/test/temba-run-list.test.js.map +1 -0
  45. package/out-tsc/test/temba-select.test.js +16 -0
  46. package/out-tsc/test/temba-select.test.js.map +1 -1
  47. package/out-tsc/test/temba-toast.test.js +299 -0
  48. package/out-tsc/test/temba-toast.test.js.map +1 -0
  49. package/out-tsc/test/temba-utils-index.test.js +1178 -0
  50. package/out-tsc/test/temba-utils-index.test.js.map +1 -0
  51. package/out-tsc/test/temba-webchat.test.js +816 -0
  52. package/out-tsc/test/temba-webchat.test.js.map +1 -0
  53. package/out-tsc/test/utils.test.js +3 -1
  54. package/out-tsc/test/utils.test.js.map +1 -1
  55. package/package.json +6 -8
  56. package/screenshots/truth/alert/error.png +0 -0
  57. package/screenshots/truth/alert/info.png +0 -0
  58. package/screenshots/truth/alert/warning.png +0 -0
  59. package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
  60. package/screenshots/truth/checkbox/checked.png +0 -0
  61. package/screenshots/truth/checkbox/default.png +0 -0
  62. package/screenshots/truth/colorpicker/default.png +0 -0
  63. package/screenshots/truth/colorpicker/focused.png +0 -0
  64. package/screenshots/truth/colorpicker/initialized.png +0 -0
  65. package/screenshots/truth/colorpicker/selected.png +0 -0
  66. package/screenshots/truth/compose/attachments-tab.png +0 -0
  67. package/screenshots/truth/compose/attachments-with-files-focused.png +0 -0
  68. package/screenshots/truth/compose/attachments-with-files.png +0 -0
  69. package/screenshots/truth/compose/intial-text.png +0 -0
  70. package/screenshots/truth/compose/no-counter.png +0 -0
  71. package/screenshots/truth/compose/wraps-text-and-spaces.png +0 -0
  72. package/screenshots/truth/compose/wraps-text-and-url.png +0 -0
  73. package/screenshots/truth/compose/wraps-text-no-spaces.png +0 -0
  74. package/screenshots/truth/contacts/badges.png +0 -0
  75. package/screenshots/truth/contacts/chat-failure.png +0 -0
  76. package/screenshots/truth/contacts/chat-for-active-contact.png +0 -0
  77. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  78. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  79. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  80. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  81. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  82. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  83. package/screenshots/truth/content-menu/button-no-items.png +0 -0
  84. package/screenshots/truth/content-menu/items-and-buttons.png +0 -0
  85. package/screenshots/truth/counter/summary.png +0 -0
  86. package/screenshots/truth/counter/text.png +0 -0
  87. package/screenshots/truth/counter/unicode-variables.png +0 -0
  88. package/screenshots/truth/counter/unicode.png +0 -0
  89. package/screenshots/truth/counter/variable.png +0 -0
  90. package/screenshots/truth/date/date-inline.png +0 -0
  91. package/screenshots/truth/date/date.png +0 -0
  92. package/screenshots/truth/date/datetime.png +0 -0
  93. package/screenshots/truth/date/duration.png +0 -0
  94. package/screenshots/truth/date/timedate.png +0 -0
  95. package/screenshots/truth/datepicker/date-truncated-time.png +0 -0
  96. package/screenshots/truth/datepicker/date.png +0 -0
  97. package/screenshots/truth/datepicker/initial-timezone.png +0 -0
  98. package/screenshots/truth/datepicker/updated-keyboard-date.png +0 -0
  99. package/screenshots/truth/dialog/focused.png +0 -0
  100. package/screenshots/truth/dropdown/after-blur.png +0 -0
  101. package/screenshots/truth/dropdown/bottom-edge-collision.png +0 -0
  102. package/screenshots/truth/dropdown/custom-arrow-size.png +0 -0
  103. package/screenshots/truth/dropdown/default.png +0 -0
  104. package/screenshots/truth/dropdown/narrow-toggle.png +0 -0
  105. package/screenshots/truth/dropdown/no-mask.png +0 -0
  106. package/screenshots/truth/dropdown/opened.png +0 -0
  107. package/screenshots/truth/dropdown/positioned.png +0 -0
  108. package/screenshots/truth/dropdown/right-edge-collision.png +0 -0
  109. package/screenshots/truth/dropdown/with-mask.png +0 -0
  110. package/screenshots/truth/label/custom.png +0 -0
  111. package/screenshots/truth/label/danger.png +0 -0
  112. package/screenshots/truth/label/dark.png +0 -0
  113. package/screenshots/truth/label/default-icon.png +0 -0
  114. package/screenshots/truth/label/no-icon.png +0 -0
  115. package/screenshots/truth/label/primary.png +0 -0
  116. package/screenshots/truth/label/secondary.png +0 -0
  117. package/screenshots/truth/label/shadow.png +0 -0
  118. package/screenshots/truth/label/tertiary.png +0 -0
  119. package/screenshots/truth/lightbox/img-zoomed.png +0 -0
  120. package/screenshots/truth/list/fields-dragging.png +0 -0
  121. package/screenshots/truth/list/fields-filtered.png +0 -0
  122. package/screenshots/truth/list/fields-hovered.png +0 -0
  123. package/screenshots/truth/list/fields.png +0 -0
  124. package/screenshots/truth/list/items-selected.png +0 -0
  125. package/screenshots/truth/list/items-updated.png +0 -0
  126. package/screenshots/truth/list/items.png +0 -0
  127. package/screenshots/truth/list/sortable-dragging.png +0 -0
  128. package/screenshots/truth/list/sortable-dropped.png +0 -0
  129. package/screenshots/truth/list/sortable.png +0 -0
  130. package/screenshots/truth/menu/menu-focused-with items.png +0 -0
  131. package/screenshots/truth/menu/menu-refresh-1.png +0 -0
  132. package/screenshots/truth/menu/menu-refresh-2.png +0 -0
  133. package/screenshots/truth/menu/menu-root.png +0 -0
  134. package/screenshots/truth/menu/menu-submenu.png +0 -0
  135. package/screenshots/truth/menu/menu-tasks-nextup.png +0 -0
  136. package/screenshots/truth/menu/menu-tasks.png +0 -0
  137. package/screenshots/truth/modax/form.png +0 -0
  138. package/screenshots/truth/modax/simple.png +0 -0
  139. package/screenshots/truth/omnibox/selected.png +0 -0
  140. package/screenshots/truth/options/block.png +0 -0
  141. package/screenshots/truth/run-list/basic.png +0 -0
  142. package/screenshots/truth/select/disabled-multi-selection.png +0 -0
  143. package/screenshots/truth/select/disabled-selection.png +0 -0
  144. package/screenshots/truth/select/disabled.png +0 -0
  145. package/screenshots/truth/select/embedded.png +0 -0
  146. package/screenshots/truth/select/empty-options.png +0 -0
  147. package/screenshots/truth/select/expression-selected.png +0 -0
  148. package/screenshots/truth/select/expressions.png +0 -0
  149. package/screenshots/truth/select/functions.png +0 -0
  150. package/screenshots/truth/select/local-options.png +0 -0
  151. package/screenshots/truth/select/multi-with-endpoint.png +0 -0
  152. package/screenshots/truth/select/multiple-initial-values.png +0 -0
  153. package/screenshots/truth/select/remote-options.png +0 -0
  154. package/screenshots/truth/select/search-enabled.png +0 -0
  155. package/screenshots/truth/select/search-multi-no-matches.png +0 -0
  156. package/screenshots/truth/select/search-selected-focus.png +0 -0
  157. package/screenshots/truth/select/search-selected.png +0 -0
  158. package/screenshots/truth/select/search-with-selected.png +0 -0
  159. package/screenshots/truth/select/searching.png +0 -0
  160. package/screenshots/truth/select/selected-multi-maxitems-reached.png +0 -0
  161. package/screenshots/truth/select/selected-multi.png +0 -0
  162. package/screenshots/truth/select/selected-single.png +0 -0
  163. package/screenshots/truth/select/selection-clearable.png +0 -0
  164. package/screenshots/truth/select/static-initial-value.png +0 -0
  165. package/screenshots/truth/select/static-initial-via-selected.png +0 -0
  166. package/screenshots/truth/select/truncated-selection.png +0 -0
  167. package/screenshots/truth/select/with-placeholder.png +0 -0
  168. package/screenshots/truth/select/without-placeholder.png +0 -0
  169. package/screenshots/truth/slider/custom-min-custom-max-valid-value.png +0 -0
  170. package/screenshots/truth/slider/custom-min-default-max-no-value.png +0 -0
  171. package/screenshots/truth/slider/default-min-custom-max-no-value.png +0 -0
  172. package/screenshots/truth/slider/default-min-default-max-invalid-value.png +0 -0
  173. package/screenshots/truth/slider/default-min-default-max-valid-value.png +0 -0
  174. package/screenshots/truth/slider/update-slider-on-value-change.png +0 -0
  175. package/screenshots/truth/templates/default.png +0 -0
  176. package/screenshots/truth/templates/unapproved.png +0 -0
  177. package/screenshots/truth/textinput/input-disabled.png +0 -0
  178. package/screenshots/truth/textinput/input-focused.png +0 -0
  179. package/screenshots/truth/textinput/input-form.png +0 -0
  180. package/screenshots/truth/textinput/input-inserted.png +0 -0
  181. package/screenshots/truth/textinput/input-placeholder.png +0 -0
  182. package/screenshots/truth/textinput/input-updated.png +0 -0
  183. package/screenshots/truth/textinput/input.png +0 -0
  184. package/screenshots/truth/textinput/textarea-focused.png +0 -0
  185. package/screenshots/truth/textinput/textarea.png +0 -0
  186. package/screenshots/truth/tip/bottom.png +0 -0
  187. package/screenshots/truth/tip/left.png +0 -0
  188. package/screenshots/truth/tip/right.png +0 -0
  189. package/screenshots/truth/tip/top.png +0 -0
  190. package/screenshots/truth/webchat/closed-widget.png +0 -0
  191. package/screenshots/truth/webchat/connected-state.png +0 -0
  192. package/screenshots/truth/webchat/connecting-state.png +0 -0
  193. package/screenshots/truth/webchat/disconnected-state.png +0 -0
  194. package/screenshots/truth/webchat/opened-widget.png +0 -0
  195. package/src/chart/TembaChart.ts +86 -15
  196. package/src/list/RunList.ts +11 -8
  197. package/src/locales/es.ts +1 -0
  198. package/src/locales/fr.ts +1 -0
  199. package/src/locales/pt.ts +1 -0
  200. package/src/options/Options.ts +38 -13
  201. package/src/select/Select.ts +32 -5
  202. package/src/store/AppState.ts +3 -3
  203. package/src/utils/index.ts +17 -5
  204. package/src/vectoricon/VectorIcon.ts +2 -1
  205. package/temba-modules.ts +0 -2
  206. package/test/temba-appstate-language.test.ts +218 -0
  207. package/test/temba-chart.test.ts +161 -1
  208. package/test/temba-dropdown.test.ts +444 -0
  209. package/test/temba-run-list.test.ts +774 -0
  210. package/test/temba-select.test.ts +27 -0
  211. package/test/temba-toast.test.ts +386 -0
  212. package/test/temba-utils-index.test.ts +1547 -0
  213. package/test/temba-webchat.test.ts +1095 -0
  214. package/test/utils.test.ts +4 -2
  215. package/test-assets/list/flow-results.json +17 -0
  216. package/test-assets/list/runs.json +126 -0
  217. package/test-assets/style.css +23 -0
  218. package/web-test-runner.config.mjs +33 -7
  219. package/xliff/es.xlf +3 -0
  220. package/xliff/fr.xlf +3 -0
  221. package/xliff/pt.xlf +3 -0
  222. package/out-tsc/src/outboxmonitor/OutboxMonitor.js +0 -136
  223. package/out-tsc/src/outboxmonitor/OutboxMonitor.js.map +0 -1
  224. package/src/outboxmonitor/OutboxMonitor.ts +0 -148
Binary file
Binary file
Binary file
Binary file
@@ -6,35 +6,66 @@ import { Select, SelectOption } from '../select/Select';
6
6
  import { getClasses } from '../utils';
7
7
  import { getStore } from '../store/Store';
8
8
 
9
+ // eslint-disable-next-line import/no-named-as-default
9
10
  import Chart, { ChartType } from 'chart.js/auto';
10
11
  import 'chartjs-adapter-luxon';
11
12
 
12
13
  const colors = [
14
+ 'rgba(54, 162, 235, 0.2)',
13
15
  'rgba(255, 159, 64, 0.2)',
14
16
  'rgba(75, 192, 192, 0.2)',
15
- 'rgba(54, 162, 235, 0.2)',
16
17
  'rgba(153, 102, 255, 0.2)',
17
18
  'rgba(255, 205, 86, 0.2)',
18
- 'rgba(255, 99, 132, 0.2)',
19
- 'rgba(201, 203, 207, 0.2)'
19
+ 'rgba(255, 99, 132, 0.2)'
20
20
  ];
21
21
 
22
22
  const colorsBorder = [
23
+ 'rgb(54, 162, 235)',
23
24
  'rgb(255, 159, 64)',
24
25
  'rgb(75, 192, 192)',
25
- 'rgb(54, 162, 235)',
26
26
  'rgb(153, 102, 255)',
27
27
  'rgb(255, 205, 86)',
28
- 'rgb(255, 99, 132)',
29
- 'rgb(201, 203, 207)'
28
+ 'rgb(255, 99, 132)'
30
29
  ];
31
30
 
32
- const allBorderColor = 'rgb(54, 162, 235)';
33
- const allBackgroundColor = 'rgba(54, 162, 235, 0.2)';
34
-
35
31
  const otherBackgroundColor = 'rgba(201, 203, 207, 0.2)';
36
32
  const otherBorderColor = 'rgb(201, 203, 207)';
37
33
 
34
+ /**
35
+ * Formats a duration in seconds to a human-readable string showing the two largest units.
36
+ * Examples: 68787 -> "19h 6m", 958000 -> "11d 2h", 3661 -> "1h 1m"
37
+ */
38
+ export function formatDurationFromSeconds(seconds: number): string {
39
+ if (seconds === 0) {
40
+ return '0s';
41
+ }
42
+
43
+ const totalDays = Math.floor(seconds / 86400);
44
+ const remainingAfterDays = seconds % 86400;
45
+ const remainingHours = Math.floor(remainingAfterDays / 3600);
46
+ const remainingAfterHours = remainingAfterDays % 3600;
47
+ const remainingMinutes = Math.floor(remainingAfterHours / 60);
48
+ const remainingSeconds = remainingAfterHours % 60;
49
+
50
+ const units = [];
51
+
52
+ if (totalDays > 0) {
53
+ units.push(`${totalDays}d`);
54
+ }
55
+ if (remainingHours > 0) {
56
+ units.push(`${remainingHours}h`);
57
+ }
58
+ if (remainingMinutes > 0 && units.length < 2) {
59
+ units.push(`${remainingMinutes}m`);
60
+ }
61
+ if (remainingSeconds > 0 && units.length < 2) {
62
+ units.push(`${remainingSeconds}s`);
63
+ }
64
+
65
+ // Return the first two most significant units
66
+ return units.slice(0, 2).join(' ');
67
+ }
68
+
38
69
  export interface RapidChartData {
39
70
  labels: string[];
40
71
  datasets: { label: string; data: number[] }[];
@@ -68,9 +99,21 @@ export class TembaChart extends RapidElement {
68
99
  @property({ type: String })
69
100
  dataname = 'Counts';
70
101
 
102
+ @property({ type: Boolean })
103
+ single: boolean = false;
104
+
105
+ @property({ type: Boolean })
106
+ legend: boolean = false;
107
+
71
108
  @property({ type: Boolean })
72
109
  config: boolean = false;
73
110
 
111
+ @property({ type: Boolean })
112
+ formatDuration: boolean = false;
113
+
114
+ @property({ type: Number })
115
+ colorIndex: number = 0;
116
+
74
117
  @state()
75
118
  showConfig: boolean = false;
76
119
 
@@ -180,8 +223,12 @@ export class TembaChart extends RapidElement {
180
223
  } else {
181
224
  datasets.push({
182
225
  ...dataset,
183
- backgroundColor: colors[datasets.length % colors.length],
184
- borderColor: colorsBorder[datasets.length % colorsBorder.length],
226
+ backgroundColor:
227
+ colors[(datasets.length + this.colorIndex) % colors.length],
228
+ borderColor:
229
+ colorsBorder[
230
+ (datasets.length + this.colorIndex) % colorsBorder.length
231
+ ],
185
232
  borderWidth: 1
186
233
  });
187
234
  }
@@ -189,10 +236,10 @@ export class TembaChart extends RapidElement {
189
236
 
190
237
  if (datasets.length === 0) {
191
238
  datasets.push({
192
- label: `All ${this.dataname}`,
239
+ label: this.single ? this.dataname : `All ${this.dataname}`,
193
240
  data: sums,
194
- backgroundColor: allBackgroundColor,
195
- borderColor: allBorderColor,
241
+ backgroundColor: colors[this.colorIndex % colors.length],
242
+ borderColor: colorsBorder[this.colorIndex % colorsBorder.length],
196
243
  borderWidth: 1
197
244
  });
198
245
  } else {
@@ -222,6 +269,23 @@ export class TembaChart extends RapidElement {
222
269
  datasets: this.datasets
223
270
  },
224
271
  options: {
272
+ plugins: {
273
+ legend: {
274
+ display: this.legend
275
+ },
276
+ ...(this.formatDuration && {
277
+ tooltip: {
278
+ callbacks: {
279
+ label: (context: any) => {
280
+ const label = context.dataset.label || '';
281
+ const value = context.parsed.y;
282
+ const formattedValue = formatDurationFromSeconds(value);
283
+ return `${label}: ${formattedValue}`;
284
+ }
285
+ }
286
+ }
287
+ })
288
+ },
225
289
  responsive: true,
226
290
  maintainAspectRatio: false,
227
291
  animation: {
@@ -240,7 +304,14 @@ export class TembaChart extends RapidElement {
240
304
  scales: {
241
305
  y: {
242
306
  min: 0,
243
- stacked: true
307
+ stacked: true,
308
+ ...(this.formatDuration && {
309
+ ticks: {
310
+ callback: (value: any) => {
311
+ return formatDurationFromSeconds(value);
312
+ }
313
+ }
314
+ })
244
315
  },
245
316
  x: {
246
317
  type: 'time',
@@ -155,8 +155,8 @@ export class RunList extends TembaList {
155
155
  style="width:16em;white-space:nowrap;overflow: hidden; text-overflow: ellipsis;"
156
156
  >
157
157
  <temba-contact-name
158
- name=${run.contact.name || run.contact.anon_display}
159
- urn=${run.contact.urn}
158
+ name=${run.contact?.name || run.contact?.anon_display || ''}
159
+ urn=${run.contact?.urn || ''}
160
160
  icon-size="15"
161
161
  />
162
162
  </div>
@@ -255,7 +255,7 @@ export class RunList extends TembaList {
255
255
  return null;
256
256
  }
257
257
 
258
- const resultKeys = Object.keys(this.selectedRun.values);
258
+ const resultKeys = Object.keys(this.selectedRun.values || {});
259
259
 
260
260
  return html` <div
261
261
  style="margin-top: 1.5em; margin-bottom:0.5em;flex-grow:1;border-radius:var(--curvature); border: 1px solid var(--color-widget-border);"
@@ -267,11 +267,12 @@ export class RunList extends TembaList {
267
267
  <div>
268
268
  <temba-contact-name
269
269
  style="cursor:pointer"
270
- name=${this.selectedRun.contact.name ||
271
- this.selectedRun.contact.anon_display}
272
- urn=${this.selectedRun.contact.urn}
270
+ name=${this.selectedRun.contact?.name ||
271
+ this.selectedRun.contact?.anon_display ||
272
+ ''}
273
+ urn=${this.selectedRun.contact?.urn || ''}
273
274
  onclick="goto(event, this)"
274
- href="/contact/read/${this.selectedRun.contact.uuid}/"
275
+ href="/contact/read/${this.selectedRun.contact?.uuid || ''}/"
275
276
  ></temba-contact-name>
276
277
  <div
277
278
  style="display:flex;margin-left:-0.2em;margin-top:0.25em;font-size: 0.65em"
@@ -303,7 +304,9 @@ export class RunList extends TembaList {
303
304
  <div style="flex-grow:1"></div>
304
305
  <div style="display:flex;flex-direction: column">
305
306
  <div style="font-size:0.75em">
306
- ${new Date(this.selectedRun.created_on).toLocaleString()}
307
+ ${this.selectedRun.created_on
308
+ ? new Date(this.selectedRun.created_on).toLocaleString()
309
+ : ''}
307
310
  </div>
308
311
  <div
309
312
  style="font-size:0.6em;align-self:flex-end;color:#888;line-height:0.75em"
package/src/locales/es.ts CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  export const templates = {
8
8
  scf1453991c986b25: `Tab para completar, enter para seleccionar`,
9
+ s73b4d70c02f4b4e0: `No options`,
9
10
  s8f02e3a18ffc083a: `Are not currently in a flow`,
10
11
  s638236250662c6b3: `Have sent a message in the last`,
11
12
  s4788ee206c4570c7: `Have not started this flow in the last 90 days`
package/src/locales/fr.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  /* eslint-disable @typescript-eslint/no-explicit-any */
6
6
 
7
7
  export const templates = {
8
+ s73b4d70c02f4b4e0: `No options`,
8
9
  scf1453991c986b25: `Tab to complete, enter to select`,
9
10
  s8f02e3a18ffc083a: `Are not currently in a flow`,
10
11
  s638236250662c6b3: `Have sent a message in the last`,
package/src/locales/pt.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  /* eslint-disable @typescript-eslint/no-explicit-any */
6
6
 
7
7
  export const templates = {
8
+ s73b4d70c02f4b4e0: `No options`,
8
9
  scf1453991c986b25: `Tab to complete, enter to select`,
9
10
  s8f02e3a18ffc083a: `Are not currently in a flow`,
10
11
  s638236250662c6b3: `Have sent a message in the last`,
@@ -4,6 +4,7 @@ import { CustomEventType } from '../interfaces';
4
4
  import { RapidElement, EventHandler } from '../RapidElement';
5
5
  import { styleMap } from 'lit-html/directives/style-map.js';
6
6
  import { getClasses, getScrollParent, throttle } from '../utils';
7
+ import { msg } from '@lit/localize';
7
8
 
8
9
  export class Options extends RapidElement {
9
10
  static get styles() {
@@ -177,6 +178,15 @@ export class Options extends RapidElement {
177
178
  background: var(--color-selection);
178
179
  color: var(--color-text-dark);
179
180
  }
181
+
182
+ .option.no-options {
183
+ pointer-events: none;
184
+ }
185
+
186
+ .option.no-options:hover {
187
+ background: transparent;
188
+ color: var(--color-text-dark-secondary);
189
+ }
180
190
  `;
181
191
  }
182
192
 
@@ -276,6 +286,9 @@ export class Options extends RapidElement {
276
286
  @property({ type: Boolean })
277
287
  triggerScroll = false;
278
288
 
289
+ @property({ type: Boolean })
290
+ showEmptyMessage = false;
291
+
279
292
  scrollParent: HTMLElement = null;
280
293
 
281
294
  resolvedRenderOption: { (option: any, selected: boolean): TemplateResult };
@@ -675,19 +688,31 @@ export class Options extends RapidElement {
675
688
  <div class=${classes} style=${styleMap(containerStyle)}>
676
689
  <div class="options-scroll" @scroll=${this.handleInnerScroll}>
677
690
  <div class="${classesInner}" style=${styleMap(optionsStyle)}>
678
- ${options.map((option, index) => {
679
- return html`<div
680
- data-option-index="${index}"
681
- @mousemove=${this.handleMouseMove}
682
- @mousedown=${this.handleOptionClick}
683
- class="option ${index === this.cursorIndex &&
684
- !this.internalFocusDisabled
685
- ? 'focused'
686
- : ''}"
687
- >
688
- ${this.resolvedRenderOption(option, index === this.cursorIndex)}
689
- </div>`;
690
- })}
691
+ ${options.length > 0
692
+ ? options.map((option, index) => {
693
+ return html`<div
694
+ data-option-index="${index}"
695
+ @mousemove=${this.handleMouseMove}
696
+ @mousedown=${this.handleOptionClick}
697
+ class="option ${index === this.cursorIndex &&
698
+ !this.internalFocusDisabled
699
+ ? 'focused'
700
+ : ''}"
701
+ >
702
+ ${this.resolvedRenderOption(
703
+ option,
704
+ index === this.cursorIndex
705
+ )}
706
+ </div>`;
707
+ })
708
+ : this.visible && this.showEmptyMessage
709
+ ? html`<div
710
+ class="option no-options"
711
+ style="color: var(--color-text-dark-secondary); cursor: default;"
712
+ >
713
+ ${msg('No options')}
714
+ </div>`
715
+ : null}
691
716
  ${this.block ? html`<div style="height:0.1em"></div>` : null}
692
717
  </div>
693
718
  <slot></slot>
@@ -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,
@@ -410,6 +410,9 @@ export class Select<T extends SelectOption> extends FormElement {
410
410
  @property({ type: Boolean })
411
411
  disabled = false;
412
412
 
413
+ @state()
414
+ attemptedOpen = false;
415
+
413
416
  @property({ attribute: false })
414
417
  selectedIndex = -1;
415
418
 
@@ -863,6 +866,7 @@ export class Select<T extends SelectOption> extends FormElement {
863
866
  }
864
867
 
865
868
  this.visibleOptions = [];
869
+ this.attemptedOpen = false;
866
870
  this.input = '';
867
871
  this.next = null;
868
872
  this.complete = true;
@@ -955,7 +959,9 @@ export class Select<T extends SelectOption> extends FormElement {
955
959
  }
956
960
 
957
961
  public isOpen(): boolean {
958
- return this.visibleOptions.length > 0;
962
+ return (
963
+ this.visibleOptions.length > 0 || (this.attemptedOpen && this.focused)
964
+ );
959
965
  }
960
966
 
961
967
  public setOptions(options: any[]): void {
@@ -1212,6 +1218,7 @@ export class Select<T extends SelectOption> extends FormElement {
1212
1218
 
1213
1219
  private handleBlur() {
1214
1220
  this.focused = false;
1221
+ this.attemptedOpen = false;
1215
1222
  if (this.visibleOptions.length > 0) {
1216
1223
  this.input = '';
1217
1224
  this.next = null;
@@ -1285,8 +1292,10 @@ export class Select<T extends SelectOption> extends FormElement {
1285
1292
  ) {
1286
1293
  if (
1287
1294
  this.visibleOptions.length === 0 &&
1288
- this.completionOptions.length === 0
1295
+ this.completionOptions.length === 0 &&
1296
+ !this.input
1289
1297
  ) {
1298
+ this.attemptedOpen = true;
1290
1299
  this.requestUpdate('input');
1291
1300
  return;
1292
1301
  }
@@ -1322,6 +1331,7 @@ export class Select<T extends SelectOption> extends FormElement {
1322
1331
 
1323
1332
  private handleCancel() {
1324
1333
  this.visibleOptions = [];
1334
+ this.attemptedOpen = false;
1325
1335
  }
1326
1336
 
1327
1337
  private handleCursorChanged(event: CustomEvent) {
@@ -1340,10 +1350,15 @@ export class Select<T extends SelectOption> extends FormElement {
1340
1350
  return;
1341
1351
  }
1342
1352
 
1343
- if (this.visibleOptions.length > 0) {
1353
+ // Check if dropdown is currently open (either with options or showing "No options")
1354
+ if (this.isOpen()) {
1344
1355
  this.visibleOptions = [];
1356
+ this.attemptedOpen = false;
1345
1357
  } else {
1358
+ this.attemptedOpen = true;
1346
1359
  this.requestUpdate('input');
1360
+ // Also trigger an immediate update to show empty dropdown
1361
+ this.requestUpdate();
1347
1362
  }
1348
1363
  }
1349
1364
  }
@@ -1461,6 +1476,17 @@ export class Select<T extends SelectOption> extends FormElement {
1461
1476
  this.requestUpdate('values', oldValues);
1462
1477
  }
1463
1478
 
1479
+ private shouldShowEmptyMessage(): boolean {
1480
+ return (
1481
+ this.attemptedOpen &&
1482
+ this.focused &&
1483
+ this.visibleOptions.length === 0 &&
1484
+ !this.input &&
1485
+ this.staticOptions.length === 0 &&
1486
+ !this.endpoint
1487
+ );
1488
+ }
1489
+
1464
1490
  public render(): TemplateResult {
1465
1491
  const placeholder = this.values.length === 0 ? this.placeholder : '';
1466
1492
  const placeholderDiv = html`
@@ -1620,7 +1646,8 @@ export class Select<T extends SelectOption> extends FormElement {
1620
1646
  .getName=${this.getNameInternal}
1621
1647
  ?static-width=${this.optionWidth}
1622
1648
  ?anchor-right=${this.anchorRight}
1623
- ?visible=${this.visibleOptions.length > 0}
1649
+ ?visible=${this.visibleOptions.length > 0 || this.shouldShowEmptyMessage()}
1650
+ ?showEmptyMessage=${this.shouldShowEmptyMessage()}
1624
1651
  ></temba-options>
1625
1652
 
1626
1653
  <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 {
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);