@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.
- package/CHANGELOG.md +8 -0
- package/dist/{a439f561.js → dd72d92e.js} +256 -71
- package/dist/index.js +256 -71
- package/dist/static/icons/symbol-defs.svg +10 -20
- package/dist/sw.js +1 -1
- package/dist/sw.js.map +1 -1
- package/dist/templates/components-body.html +1 -1
- package/dist/templates/components-head.html +1 -1
- package/out-tsc/src/contacts/ContactName.js +19 -16
- package/out-tsc/src/contacts/ContactName.js.map +1 -1
- package/out-tsc/src/contacts/ContactNameFetch.js +36 -0
- package/out-tsc/src/contacts/ContactNameFetch.js.map +1 -0
- package/out-tsc/src/contacts/ContactUrn.js +12 -1
- package/out-tsc/src/contacts/ContactUrn.js.map +1 -1
- package/out-tsc/src/flow/FlowStoreElement.js +43 -0
- package/out-tsc/src/flow/FlowStoreElement.js.map +1 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/list/RunList.js +317 -0
- package/out-tsc/src/list/RunList.js.map +1 -0
- package/out-tsc/src/list/TembaList.js +38 -14
- package/out-tsc/src/list/TembaList.js.map +1 -1
- package/out-tsc/src/options/Options.js +18 -2
- package/out-tsc/src/options/Options.js.map +1 -1
- package/out-tsc/src/store/Store.js +13 -3
- package/out-tsc/src/store/Store.js.map +1 -1
- package/out-tsc/src/tabpane/TabPane.js +3 -1
- package/out-tsc/src/tabpane/TabPane.js.map +1 -1
- package/out-tsc/src/utils/index.js +1 -0
- package/out-tsc/src/utils/index.js.map +1 -1
- package/out-tsc/src/vectoricon/VectorIcon.js +6 -6
- package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
- package/out-tsc/temba-modules.js +6 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/utils.test.js +1 -1
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +1 -1
- package/src/contacts/ContactName.ts +19 -17
- package/src/contacts/ContactNameFetch.ts +32 -0
- package/src/contacts/ContactUrn.ts +12 -1
- package/src/flow/FlowStoreElement.ts +42 -0
- package/src/interfaces.ts +19 -0
- package/src/list/RunList.ts +353 -0
- package/src/list/TembaList.ts +50 -14
- package/src/options/Options.ts +17 -2
- package/src/store/Store.ts +20 -3
- package/src/tabpane/TabPane.ts +3 -1
- package/src/utils/index.ts +3 -0
- package/src/vectoricon/VectorIcon.ts +5 -5
- package/static/css/temba-components.css +1 -1
- package/static/icons/Read Me.txt +15 -15
- package/static/icons/SVG/hourglass.svg +5 -0
- package/static/icons/demo-external-svg.html +142 -157
- package/static/icons/demo-files/demo.css +4 -4
- package/static/icons/demo.html +152 -177
- package/static/icons/selection.json +396 -339
- package/static/icons/style.css +0 -4
- package/static/icons/symbol-defs.svg +10 -20
- package/temba-modules.ts +6 -0
- package/test/utils.test.ts +1 -1
package/src/list/TembaList.ts
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
474
|
+
${this.renderFooter()}
|
|
475
|
+
`;
|
|
440
476
|
}
|
|
441
477
|
}
|
package/src/options/Options.ts
CHANGED
|
@@ -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({
|
package/src/store/Store.ts
CHANGED
|
@@ -179,11 +179,28 @@ export class Store extends RapidElement {
|
|
|
179
179
|
return 'en';
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
public getShortDuration(
|
|
183
|
-
|
|
184
|
-
|
|
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,
|
package/src/tabpane/TabPane.ts
CHANGED
|
@@ -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"
|
|
180
|
+
? html`<div class="count">
|
|
181
|
+
${tab.count.toLocaleString()}
|
|
182
|
+
</div>`
|
|
181
183
|
: null}
|
|
182
184
|
</div>
|
|
183
185
|
`
|
package/src/utils/index.ts
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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.
|
|
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.
|
|
214
|
-
? this.
|
|
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:
|
|
107
|
+
--icon-color-circle-hover: rgba(245, 245, 245, .8);
|
|
108
108
|
|
|
109
109
|
--transition-speed: 250ms;
|
|
110
110
|
--event-padding: 0.5em 1em;
|
package/static/icons/Read Me.txt
CHANGED
|
@@ -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-
|
|
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>
|