@nyaruka/temba-components 0.29.3 → 0.30.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 (59) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{a439f561.js → dd72d92e.js} +256 -71
  3. package/dist/index.js +256 -71
  4. package/dist/static/icons/symbol-defs.svg +10 -20
  5. package/dist/sw.js +1 -1
  6. package/dist/sw.js.map +1 -1
  7. package/dist/templates/components-body.html +1 -1
  8. package/dist/templates/components-head.html +1 -1
  9. package/out-tsc/src/contacts/ContactName.js +19 -16
  10. package/out-tsc/src/contacts/ContactName.js.map +1 -1
  11. package/out-tsc/src/contacts/ContactNameFetch.js +36 -0
  12. package/out-tsc/src/contacts/ContactNameFetch.js.map +1 -0
  13. package/out-tsc/src/contacts/ContactUrn.js +12 -1
  14. package/out-tsc/src/contacts/ContactUrn.js.map +1 -1
  15. package/out-tsc/src/flow/FlowStoreElement.js +43 -0
  16. package/out-tsc/src/flow/FlowStoreElement.js.map +1 -0
  17. package/out-tsc/src/interfaces.js.map +1 -1
  18. package/out-tsc/src/list/RunList.js +317 -0
  19. package/out-tsc/src/list/RunList.js.map +1 -0
  20. package/out-tsc/src/list/TembaList.js +38 -14
  21. package/out-tsc/src/list/TembaList.js.map +1 -1
  22. package/out-tsc/src/options/Options.js +18 -2
  23. package/out-tsc/src/options/Options.js.map +1 -1
  24. package/out-tsc/src/store/Store.js +13 -3
  25. package/out-tsc/src/store/Store.js.map +1 -1
  26. package/out-tsc/src/tabpane/TabPane.js +3 -1
  27. package/out-tsc/src/tabpane/TabPane.js.map +1 -1
  28. package/out-tsc/src/utils/index.js +1 -0
  29. package/out-tsc/src/utils/index.js.map +1 -1
  30. package/out-tsc/src/vectoricon/VectorIcon.js +6 -6
  31. package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
  32. package/out-tsc/temba-modules.js +6 -0
  33. package/out-tsc/temba-modules.js.map +1 -1
  34. package/out-tsc/test/utils.test.js +1 -1
  35. package/out-tsc/test/utils.test.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/contacts/ContactName.ts +19 -17
  38. package/src/contacts/ContactNameFetch.ts +32 -0
  39. package/src/contacts/ContactUrn.ts +12 -1
  40. package/src/flow/FlowStoreElement.ts +42 -0
  41. package/src/interfaces.ts +19 -0
  42. package/src/list/RunList.ts +353 -0
  43. package/src/list/TembaList.ts +50 -14
  44. package/src/options/Options.ts +17 -2
  45. package/src/store/Store.ts +20 -3
  46. package/src/tabpane/TabPane.ts +3 -1
  47. package/src/utils/index.ts +3 -0
  48. package/src/vectoricon/VectorIcon.ts +5 -5
  49. package/static/css/temba-components.css +1 -1
  50. package/static/icons/Read Me.txt +15 -15
  51. package/static/icons/SVG/hourglass.svg +5 -0
  52. package/static/icons/demo-external-svg.html +142 -157
  53. package/static/icons/demo-files/demo.css +4 -4
  54. package/static/icons/demo.html +152 -177
  55. package/static/icons/selection.json +396 -339
  56. package/static/icons/style.css +0 -4
  57. package/static/icons/symbol-defs.svg +10 -20
  58. package/temba-modules.ts +6 -0
  59. package/test/utils.test.ts +1 -1
@@ -2,6 +2,7 @@ import { css, html, TemplateResult } from 'lit';
2
2
  import { property } from 'lit/decorators';
3
3
  import { CustomEventType } from '../interfaces';
4
4
  import { RapidElement } from '../RapidElement';
5
+ import { Store } from '../store/Store';
5
6
  import { fetchResultsPage, ResultsPage } from '../utils';
6
7
 
7
8
  const DEFAULT_REFRESH = 10000;
@@ -37,6 +38,9 @@ export class TembaList extends RapidElement {
37
38
  @property({ type: Boolean })
38
39
  collapsed: boolean;
39
40
 
41
+ @property({ type: Boolean })
42
+ hideShadow: boolean;
43
+
40
44
  @property({ attribute: false })
41
45
  getNextRefresh: (firstOption: any) => any;
42
46
 
@@ -56,6 +60,8 @@ export class TembaList extends RapidElement {
56
60
  @property({ type: String })
57
61
  refreshKey = '0';
58
62
 
63
+ reverseRefresh = true;
64
+
59
65
  // our next page from our endpoint
60
66
  nextPage: string = null;
61
67
 
@@ -63,6 +69,8 @@ export class TembaList extends RapidElement {
63
69
  clearRefreshTimeout: any;
64
70
  pending: AbortController[] = [];
65
71
 
72
+ store: Store;
73
+
66
74
  // used for testing only
67
75
  preserve: boolean;
68
76
 
@@ -72,9 +80,6 @@ export class TembaList extends RapidElement {
72
80
  static get styles() {
73
81
  return css`
74
82
  :host {
75
- display: block;
76
- height: 100%;
77
- width: 100%;
78
83
  }
79
84
 
80
85
  temba-options {
@@ -82,18 +87,12 @@ export class TembaList extends RapidElement {
82
87
  width: 100%;
83
88
  flex-grow: 1;
84
89
  }
85
-
86
- .wrapper {
87
- display: flex;
88
- flex-direction: column;
89
- height: 100%;
90
- align-items: center;
91
- }
92
90
  `;
93
91
  }
94
92
 
95
93
  constructor() {
96
94
  super();
95
+ this.store = document.querySelector('temba-store') as Store;
97
96
  this.handleSelection.bind(this);
98
97
  }
99
98
 
@@ -139,7 +138,9 @@ export class TembaList extends RapidElement {
139
138
  }
140
139
 
141
140
  if (changedProperties.has('mostRecentItem')) {
142
- this.fireCustomEvent(CustomEventType.Refreshed);
141
+ if (this.mostRecentItem) {
142
+ this.fireCustomEvent(CustomEventType.Refreshed);
143
+ }
143
144
  }
144
145
 
145
146
  if (changedProperties.has('cursorIndex')) {
@@ -221,6 +222,11 @@ export class TembaList extends RapidElement {
221
222
  * Refreshes the first page, updating any found items in our list
222
223
  */
223
224
  private async refreshTop(): Promise<void> {
225
+ const refreshEndpoint = this.getRefreshEndpoint();
226
+ if (!refreshEndpoint) {
227
+ return;
228
+ }
229
+
224
230
  // cancel any outstanding requests
225
231
  while (this.pending.length > 0) {
226
232
  const pending = this.pending.pop();
@@ -256,7 +262,19 @@ export class TembaList extends RapidElement {
256
262
  });
257
263
 
258
264
  // insert our new items at the front
259
- const newItems = [...page.results.reverse(), ...items];
265
+ let results = page.results;
266
+ if (this.reverseRefresh) {
267
+ results = page.results.reverse();
268
+ }
269
+ const newItems = [...results, ...items];
270
+
271
+ const topItem = newItems[0];
272
+ if (
273
+ !this.mostRecentItem ||
274
+ JSON.stringify(this.mostRecentItem) !== JSON.stringify(topItem)
275
+ ) {
276
+ this.mostRecentItem = topItem;
277
+ }
260
278
 
261
279
  if (prevItem) {
262
280
  const newItem = newItems[this.cursorIndex];
@@ -324,6 +342,8 @@ export class TembaList extends RapidElement {
324
342
  } catch (error) {
325
343
  // aborted
326
344
  this.reset();
345
+
346
+ console.log('error, resetting');
327
347
  return;
328
348
  }
329
349
 
@@ -410,6 +430,18 @@ export class TembaList extends RapidElement {
410
430
  }
411
431
  }
412
432
 
433
+ public renderHeader(): TemplateResult {
434
+ return null;
435
+ }
436
+
437
+ public renderFooter(): TemplateResult {
438
+ return null;
439
+ }
440
+
441
+ public getListStyle() {
442
+ return '';
443
+ }
444
+
413
445
  private handleSelection(event: CustomEvent) {
414
446
  const { selected, index } = event.detail;
415
447
 
@@ -421,10 +453,13 @@ export class TembaList extends RapidElement {
421
453
  }
422
454
 
423
455
  public render(): TemplateResult {
424
- return html`<div class="wrapper">
456
+ return html`
457
+ ${this.renderHeader()}
425
458
  <temba-options
459
+ style="${this.getListStyle()}"
426
460
  ?visible=${true}
427
461
  ?block=${true}
462
+ ?hideShadow=${this.hideShadow}
428
463
  ?collapsed=${this.collapsed}
429
464
  ?loading=${this.loading}
430
465
  .renderOption=${this.renderOption}
@@ -436,6 +471,7 @@ export class TembaList extends RapidElement {
436
471
  >
437
472
  <slot></slot>
438
473
  </temba-options>
439
- </div>`;
474
+ ${this.renderFooter()}
475
+ `;
440
476
  }
441
477
  }
@@ -16,7 +16,6 @@ export class Options extends RapidElement {
16
16
  .options-container {
17
17
  background: var(--color-widget-bg-focused);
18
18
  user-select: none;
19
- box-shadow: var(--options-shadow);
20
19
  border-radius: var(--curvature-widget);
21
20
  overflow: hidden;
22
21
  margin-top: var(--options-margin-top);
@@ -32,6 +31,10 @@ export class Options extends RapidElement {
32
31
  border: 1px transparent;
33
32
  }
34
33
 
34
+ .shadow {
35
+ box-shadow: var(--options-shadow);
36
+ }
37
+
35
38
  .anchored {
36
39
  position: fixed;
37
40
  }
@@ -52,12 +55,19 @@ export class Options extends RapidElement {
52
55
  }
53
56
 
54
57
  :host([block]) {
55
- box-shadow: var(--options-block-shadow);
56
58
  border-radius: var(--curvature);
57
59
  display: block;
58
60
  height: 100%;
59
61
  }
60
62
 
63
+ :host([block]) .shadow {
64
+ box-shadow: var(--options-block-shadow);
65
+ }
66
+
67
+ .bordered {
68
+ border: 1px solid var(--color-widget-border) !important;
69
+ }
70
+
61
71
  :host([block]) .options {
62
72
  margin-bottom: 1.5em;
63
73
  }
@@ -218,6 +228,9 @@ export class Options extends RapidElement {
218
228
  @property({ type: Boolean })
219
229
  collapsed: boolean;
220
230
 
231
+ @property({ type: Boolean })
232
+ hideShadow = false;
233
+
221
234
  @property({ attribute: false })
222
235
  getName: { (option: any): string } = function (option: any) {
223
236
  return option[this.nameKey || 'name'];
@@ -589,6 +602,8 @@ export class Options extends RapidElement {
589
602
  top: this.poppedTop,
590
603
  anchored: !this.block,
591
604
  loading: this.loading,
605
+ shadow: !this.hideShadow,
606
+ bordered: this.hideShadow,
592
607
  });
593
608
 
594
609
  const classesInner = getClasses({
@@ -179,11 +179,28 @@ export class Store extends RapidElement {
179
179
  return 'en';
180
180
  }
181
181
 
182
- public getShortDuration(isoDate: string) {
183
- const scheduled = DateTime.fromISO(isoDate);
184
- const now = DateTime.now();
182
+ public getShortDuration(
183
+ isoDateA: string,
184
+ isoDateB: string = null,
185
+ showSeconds = false
186
+ ) {
187
+ const scheduled = DateTime.fromISO(isoDateA);
188
+ const now = isoDateB ? DateTime.fromISO(isoDateB) : DateTime.now();
185
189
 
186
190
  const duration = scheduled.diff(now).valueOf();
191
+
192
+ if (showSeconds) {
193
+ return this.humanizer.humanize(duration, {
194
+ language: this.getLanguageCode(),
195
+ largest: 1,
196
+ round: true,
197
+ });
198
+ }
199
+
200
+ if (Math.abs(duration) < 60000) {
201
+ return 'just now';
202
+ }
203
+
187
204
  return this.humanizer.humanize(duration, {
188
205
  language: this.getLanguageCode(),
189
206
  largest: 1,
@@ -177,7 +177,9 @@ export class TabPane extends RapidElement {
177
177
  ? html`
178
178
  <div class="badge">
179
179
  ${tab.count > 0
180
- ? html`<div class="count">${tab.count}</div>`
180
+ ? html`<div class="count">
181
+ ${tab.count.toLocaleString()}
182
+ </div>`
181
183
  : null}
182
184
  </div>
183
185
  `
@@ -599,3 +599,6 @@ export enum COOKIE_KEYS {
599
599
  MENU_COLLAPSED = 'menu-collapsed',
600
600
  TICKET_SHOW_DETAILS = 'tickets.show-details',
601
601
  }
602
+
603
+ export const capitalize = ([first, ...rest], locale = navigator.language) =>
604
+ first === undefined ? '' : first.toLocaleUpperCase(locale) + rest.join('');
@@ -4,7 +4,7 @@ import { property } from 'lit/decorators';
4
4
  import { getClasses } from '../utils';
5
5
 
6
6
  // for cache busting, increase whenever the icon set changes
7
- const ICON_VERSION = 12;
7
+ const ICON_VERSION = 13;
8
8
 
9
9
  export class VectorIcon extends LitElement {
10
10
  @property({ type: String })
@@ -36,7 +36,7 @@ export class VectorIcon extends LitElement {
36
36
  animationDuration = 200;
37
37
 
38
38
  @property({ type: String })
39
- href = '';
39
+ src = '';
40
40
 
41
41
  @property({ type: Number, attribute: false })
42
42
  steps = 2;
@@ -200,7 +200,7 @@ export class VectorIcon extends LitElement {
200
200
  this.steps}ms
201
201
  ${this.easing}"
202
202
  class="${getClasses({
203
- sheet: this.href === '',
203
+ sheet: this.src === '',
204
204
  [this.animateChange]: !!this.animateChange,
205
205
  [this.animateChange + '-' + this.animationStep]:
206
206
  this.animationStep > 0,
@@ -210,8 +210,8 @@ export class VectorIcon extends LitElement {
210
210
  })}"
211
211
  >
212
212
  <use
213
- href="${this.href
214
- ? this.href
213
+ href="${this.src
214
+ ? this.src
215
215
  : `${
216
216
  this.prefix || (window as any).static_url || '/static/'
217
217
  }icons/symbol-defs.svg?v=${ICON_VERSION}#icon-${
@@ -104,7 +104,7 @@
104
104
 
105
105
  --icon-color: var(--text-color);
106
106
  --icon-color-hover: var(--icon-color);
107
- --icon-color-circle-hover: rgb(245, 245, 245);
107
+ --icon-color-circle-hover: rgba(245, 245, 245, .8);
108
108
 
109
109
  --transition-speed: 250ms;
110
110
  --event-padding: 0.5em 1em;
@@ -1,15 +1,15 @@
1
- The *SVG* folder contains the icons you selected as separate SVG files.
2
-
3
- If you prefer using PNGs, PDFs, or CSS sprites, refer to the Preferences panel of the IcoMoon app before downloading your zip pack.
4
-
5
- *demo.html* lists the icons that you selected. To insert your icons as inline SVGs (with the <use> element), copy the <svg> element (that contains symbol definitions) from the source of the demo.html file, below your own HTML's <body> tag. After copying this SVG, you can reference your glyphs like the following:
6
-
7
- <svg class="icon icon-language"><use xlink:href="#icon-language"></use></svg>
8
-
9
- You can get this code from the SVG tab of the IcoMoon app, or by referring to the source of the demo.html file. To see how you can change the color/size of your icons using CSS, refer to the example provided in the *style.css* file.
10
-
11
- If you prefer to reference an external SVG (containing <defs>) instead of embedding it in HTML, you will need to use *svgxuse.js* in order to support IE 9+. In browsers that don't support referencing external SVGs (such as IE 9), this polyfill sends one HTTP request to fetch and cache all symbol definitions. See *demo-external-svg.html* for this approach. This demo references the *symbol-defs.svg* file and uses the aforementioned polyfill. Note that it must be hosted on a web server to work
12
- properly. Learn more about this polyfill here: https://github.com/Keyamoon/svgxuse
13
-
14
- You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection.
15
-
1
+ The *SVG* folder contains the icons you selected as separate SVG files.
2
+
3
+ If you prefer using PNGs, PDFs, or CSS sprites, refer to the Preferences panel of the IcoMoon app before downloading your zip pack.
4
+
5
+ *demo.html* lists the icons that you selected. To insert your icons as inline SVGs (with the <use> element), copy the <svg> element (that contains symbol definitions) from the source of the demo.html file, below your own HTML's <body> tag. After copying this SVG, you can reference your glyphs like the following:
6
+
7
+ <svg class="icon icon-hourglass"><use xlink:href="#icon-hourglass"></use></svg>
8
+
9
+ You can get this code from the SVG tab of the IcoMoon app, or by referring to the source of the demo.html file. To see how you can change the color/size of your icons using CSS, refer to the example provided in the *style.css* file.
10
+
11
+ If you prefer to reference an external SVG (containing <defs>) instead of embedding it in HTML, you will need to use *svgxuse.js* in order to support IE 9+. In browsers that don't support referencing external SVGs (such as IE 9), this polyfill sends one HTTP request to fetch and cache all symbol definitions. See *demo-external-svg.html* for this approach. This demo references the *symbol-defs.svg* file and uses the aforementioned polyfill. Note that it must be hosted on a web server to work
12
+ properly. Learn more about this polyfill here: https://github.com/Keyamoon/svgxuse
13
+
14
+ You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection.
15
+
@@ -0,0 +1,5 @@
1
+ <!-- Generated by IcoMoon.io -->
2
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
3
+ <title>hourglass</title>
4
+ <path d="M12 11.484l3.984-3.984v-3.516h-7.969v3.516zM15.984 16.5l-3.984-3.984-3.984 3.984v3.516h7.969v-3.516zM6 2.016h12v6l-3.984 3.984 3.984 3.984v6h-12v-6l3.984-3.984-3.984-3.984v-6z"></path>
5
+ </svg>