@nyaruka/temba-components 0.26.8 → 0.26.9
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 +12 -0
- package/demo/index.html +9 -1
- package/dist/{d08b61e5.js → d0cc86be.js} +386 -38
- package/dist/index.js +386 -38
- package/dist/static/icons/symbol-defs.svg +13 -1
- package/dist/static/img/schemes/email.svg +1 -0
- package/dist/static/img/schemes/facebook.svg +1 -0
- package/dist/static/img/schemes/instagram.svg +1 -0
- package/dist/static/img/schemes/line.svg +1 -0
- package/dist/static/img/schemes/messenger.svg +1 -0
- package/dist/static/img/schemes/tel.svg +34 -0
- package/dist/static/img/schemes/telegram.svg +1 -0
- package/dist/static/img/schemes/twitter.svg +1 -0
- package/dist/static/img/schemes/viber.svg +1 -0
- package/dist/static/img/schemes/vk.svg +1 -0
- package/dist/static/img/schemes/whatsapp.svg +1 -0
- 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/RapidElement.js.map +1 -1
- package/out-tsc/src/RefreshElement.js +28 -0
- package/out-tsc/src/RefreshElement.js.map +1 -0
- package/out-tsc/src/button/Button.js +4 -0
- package/out-tsc/src/button/Button.js.map +1 -1
- package/out-tsc/src/contacts/ContactChat.js +5 -13
- package/out-tsc/src/contacts/ContactChat.js.map +1 -1
- package/out-tsc/src/contacts/ContactFieldEditor.js +199 -0
- package/out-tsc/src/contacts/ContactFieldEditor.js.map +1 -0
- package/out-tsc/src/contacts/ContactFields.js +106 -0
- package/out-tsc/src/contacts/ContactFields.js.map +1 -0
- package/out-tsc/src/contacts/ContactGroups.js +39 -0
- package/out-tsc/src/contacts/ContactGroups.js.map +1 -0
- package/out-tsc/src/contacts/ContactName.js +40 -0
- package/out-tsc/src/contacts/ContactName.js.map +1 -0
- package/out-tsc/src/contacts/ContactStoreElement.js +44 -0
- package/out-tsc/src/contacts/ContactStoreElement.js.map +1 -0
- package/out-tsc/src/contacts/ContactUrn.js +38 -0
- package/out-tsc/src/contacts/ContactUrn.js.map +1 -0
- package/out-tsc/src/contacts/events.js +42 -4
- package/out-tsc/src/contacts/events.js.map +1 -1
- package/out-tsc/src/interfaces.js +1 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/label/Label.js +32 -12
- package/out-tsc/src/label/Label.js.map +1 -1
- package/out-tsc/src/select/Select.js +4 -4
- package/out-tsc/src/select/Select.js.map +1 -1
- package/out-tsc/src/store/Store.js +97 -3
- package/out-tsc/src/store/Store.js.map +1 -1
- package/out-tsc/src/store/StoreElement.js +55 -0
- package/out-tsc/src/store/StoreElement.js.map +1 -0
- package/out-tsc/src/textinput/TextInput.js +35 -17
- package/out-tsc/src/textinput/TextInput.js.map +1 -1
- package/out-tsc/src/vectoricon/VectorIcon.js +16 -14
- package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
- package/out-tsc/temba-modules.js +12 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/package.json +3 -3
- package/rollup.config.js +1 -0
- package/src/RapidElement.ts +0 -1
- package/src/RefreshElement.ts +33 -0
- package/src/button/Button.ts +4 -0
- package/src/contacts/ContactChat.ts +7 -16
- package/src/contacts/ContactFieldEditor.ts +201 -0
- package/src/contacts/ContactFields.ts +112 -0
- package/src/contacts/ContactGroups.ts +41 -0
- package/src/contacts/ContactName.ts +37 -0
- package/src/contacts/ContactStoreElement.ts +51 -0
- package/src/contacts/ContactUrn.ts +38 -0
- package/src/contacts/events.ts +41 -4
- package/src/interfaces.ts +2 -0
- package/src/label/Label.ts +30 -7
- package/src/select/Select.ts +4 -4
- package/src/store/Store.ts +124 -3
- package/src/store/StoreElement.ts +71 -0
- package/src/textinput/TextInput.ts +48 -27
- package/src/vectoricon/VectorIcon.ts +19 -14
- package/static/icons/Read Me.txt +1 -1
- package/static/icons/SVG/calendar1.svg +5 -0
- package/static/icons/SVG/corner-down-left.svg +5 -0
- package/static/icons/SVG/more-horizontal.svg +5 -0
- package/static/icons/SVG/refresh-cw.svg +5 -0
- package/static/icons/demo-external-svg.html +21 -1
- package/static/icons/demo.html +34 -2
- package/static/icons/selection.json +412 -316
- package/static/icons/symbol-defs.svg +13 -1
- package/static/img/schemes/email.svg +1 -0
- package/static/img/schemes/facebook.svg +1 -0
- package/static/img/schemes/instagram.svg +1 -0
- package/static/img/schemes/line.svg +1 -0
- package/static/img/schemes/messenger.svg +1 -0
- package/static/img/schemes/tel.svg +34 -0
- package/static/img/schemes/telegram.svg +1 -0
- package/static/img/schemes/twitter.svg +1 -0
- package/static/img/schemes/viber.svg +1 -0
- package/static/img/schemes/vk.svg +1 -0
- package/static/img/schemes/whatsapp.svg +1 -0
- package/temba-modules.ts +12 -0
package/src/contacts/events.ts
CHANGED
|
@@ -436,6 +436,10 @@ export const getEventStyles = () => {
|
|
|
436
436
|
.assigned .attn {
|
|
437
437
|
color: #777;
|
|
438
438
|
}
|
|
439
|
+
|
|
440
|
+
.attachments {
|
|
441
|
+
margin-top: 1em;
|
|
442
|
+
}
|
|
439
443
|
`;
|
|
440
444
|
};
|
|
441
445
|
|
|
@@ -674,12 +678,45 @@ export const renderAttachment = (attachment: string): TemplateResult => {
|
|
|
674
678
|
inner = html`<div class="linked" onclick="goto(event)" href="${url}"><img src="${url}" style="width:100%;height:auto;display:block"></img></a>`;
|
|
675
679
|
} else if (ext === 'pdf') {
|
|
676
680
|
return html`<div
|
|
677
|
-
|
|
678
|
-
|
|
681
|
+
style="width:100%;height:300px;border-radius:var(--curvature);box-shadow:0px 0px 12px 0px rgba(0,0,0,.1), 0px 0px 2px 0px rgba(0,0,0,.15);overflow:hidden"
|
|
682
|
+
><embed src="${url}#view=Fit" type="application/pdf" frameBorder="0" scrolling="auto" height="100%" width="100%"></embed></div>`;
|
|
679
683
|
} else if (mediaType === 'video') {
|
|
680
|
-
return html`<video
|
|
684
|
+
return html`<video
|
|
685
|
+
style="border-radius:var(--curvature);box-shadow:0px 0px 12px 0px rgba(0,0,0,.1), 0px 0px 2px 0px rgba(0,0,0,.15);"
|
|
686
|
+
max-width="400px"
|
|
687
|
+
height="auto"
|
|
688
|
+
controls="controls"
|
|
689
|
+
>
|
|
681
690
|
<source src="${url}" type="video/mp4" />
|
|
682
691
|
</video> `;
|
|
692
|
+
} else if (mediaType === 'audio') {
|
|
693
|
+
return html`<audio
|
|
694
|
+
style="border-radius: 99px; box-shadow:0px 0px 12px 0px rgba(0,0,0,.1), 0px 0px 2px 0px rgba(0,0,0,.15);"
|
|
695
|
+
src="${url}"
|
|
696
|
+
type="${attType}"
|
|
697
|
+
controls
|
|
698
|
+
>
|
|
699
|
+
<a target="_" href="${url}">${url}</a>
|
|
700
|
+
</audio>`;
|
|
701
|
+
} else if (attType === 'geo') {
|
|
702
|
+
const [lat, long] = url.split(',');
|
|
703
|
+
const latFloat = parseFloat(lat);
|
|
704
|
+
const longFloat = parseFloat(long);
|
|
705
|
+
const geo = `${lat}000000%2C${long}000000`;
|
|
706
|
+
|
|
707
|
+
return html` <iframe
|
|
708
|
+
style="border-radius: var(--curvature);box-shadow:0px 0px 12px 0px rgba(0,0,0,.1), 0px 0px 2px 0px rgba(0,0,0,.15);"
|
|
709
|
+
width="300"
|
|
710
|
+
height="300"
|
|
711
|
+
frameborder="0"
|
|
712
|
+
scrolling="no"
|
|
713
|
+
marginheight="0"
|
|
714
|
+
marginwidth="0"
|
|
715
|
+
src="https://www.openstreetmap.org/export/embed.html?bbox=${longFloat -
|
|
716
|
+
0.005}000000%2C${latFloat - 0.005}%2C${longFloat +
|
|
717
|
+
0.005}000000%2C${latFloat +
|
|
718
|
+
0.005}000000&layer=mapnik&marker=${geo}"
|
|
719
|
+
></iframe>`;
|
|
683
720
|
} else {
|
|
684
721
|
return html`<div style="display:flex">
|
|
685
722
|
<temba-icon name="download"></temba-icon>
|
|
@@ -688,7 +725,7 @@ export const renderAttachment = (attachment: string): TemplateResult => {
|
|
|
688
725
|
}
|
|
689
726
|
|
|
690
727
|
return html`<div
|
|
691
|
-
style="width:100%;max-width:300px;border-radius:var(--curvature); box-shadow:0px 0px
|
|
728
|
+
style="width:100%;max-width:300px;border-radius:var(--curvature); box-shadow:0px 0px 6px 0px rgba(0,0,0,.15);overflow:hidden"
|
|
692
729
|
>
|
|
693
730
|
${inner}
|
|
694
731
|
</div>`;
|
package/src/interfaces.ts
CHANGED
|
@@ -43,6 +43,7 @@ export interface ContactField {
|
|
|
43
43
|
label: string;
|
|
44
44
|
value_type: string;
|
|
45
45
|
pinned: boolean;
|
|
46
|
+
priority: number;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
export interface ContactGroup {
|
|
@@ -178,4 +179,5 @@ export enum CustomEventType {
|
|
|
178
179
|
Submitted = 'temba-submitted',
|
|
179
180
|
Redirected = 'temba-redirected',
|
|
180
181
|
NoPath = 'temba-no-path',
|
|
182
|
+
StoreUpdated = 'temba-store-updated',
|
|
181
183
|
}
|
package/src/label/Label.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { LitElement, TemplateResult, html, css } from 'lit';
|
|
2
|
-
import { property
|
|
2
|
+
import { property } from 'lit/decorators';
|
|
3
3
|
import { getClasses } from '../utils';
|
|
4
4
|
import { styleMap } from 'lit-html/directives/style-map';
|
|
5
5
|
|
|
6
|
-
@customElement('temba-label')
|
|
7
6
|
export default class Label extends LitElement {
|
|
8
7
|
static get styles() {
|
|
9
8
|
return css`
|
|
@@ -11,9 +10,18 @@ export default class Label extends LitElement {
|
|
|
11
10
|
display: inline-block;
|
|
12
11
|
}
|
|
13
12
|
|
|
13
|
+
slot {
|
|
14
|
+
white-space: nowrap;
|
|
15
|
+
}
|
|
16
|
+
|
|
14
17
|
.mask {
|
|
15
|
-
padding: 3px
|
|
16
|
-
border-radius:
|
|
18
|
+
padding: 3px 8px;
|
|
19
|
+
border-radius: 12px;
|
|
20
|
+
display: flex;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
temba-icon {
|
|
24
|
+
margin-right: 0.3em;
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
.label.clickable .mask:hover {
|
|
@@ -21,10 +29,9 @@ export default class Label extends LitElement {
|
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
.label {
|
|
24
|
-
|
|
25
|
-
font-size: 80%;
|
|
32
|
+
font-size: 0.8em;
|
|
26
33
|
font-weight: 400;
|
|
27
|
-
border-radius:
|
|
34
|
+
border-radius: 12px;
|
|
28
35
|
background: tomato;
|
|
29
36
|
color: #fff;
|
|
30
37
|
text-shadow: 0 0.04em 0.04em rgba(0, 0, 0, 0.35);
|
|
@@ -33,29 +40,37 @@ export default class Label extends LitElement {
|
|
|
33
40
|
.primary {
|
|
34
41
|
background: var(--color-label-primary);
|
|
35
42
|
color: var(--color-label-primary-text);
|
|
43
|
+
--icon-color: var(--color-label-primary-text);
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
.secondary {
|
|
39
47
|
background: var(--color-label-secondary);
|
|
40
48
|
color: var(--color-label-secondary-text);
|
|
49
|
+
--icon-color: var(--color-label-secondary-text);
|
|
41
50
|
text-shadow: none;
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
.light {
|
|
45
54
|
background: var(--color-overlay-light);
|
|
46
55
|
color: var(--color-overlay-light-text);
|
|
56
|
+
--icon-color: var(--color-overlay-light-text);
|
|
47
57
|
text-shadow: none;
|
|
48
58
|
}
|
|
49
59
|
|
|
50
60
|
.dark {
|
|
51
61
|
background: var(--color-overlay-dark);
|
|
52
62
|
color: var(--color-overlay-dark-text);
|
|
63
|
+
--icon-color: var(--color-overlay-dark-text);
|
|
53
64
|
text-shadow: none;
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
.clickable {
|
|
57
68
|
cursor: pointer;
|
|
58
69
|
}
|
|
70
|
+
|
|
71
|
+
.shadow {
|
|
72
|
+
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1);
|
|
73
|
+
}
|
|
59
74
|
`;
|
|
60
75
|
}
|
|
61
76
|
|
|
@@ -74,6 +89,12 @@ export default class Label extends LitElement {
|
|
|
74
89
|
@property({ type: Boolean })
|
|
75
90
|
dark: boolean;
|
|
76
91
|
|
|
92
|
+
@property({ type: Boolean })
|
|
93
|
+
shadow: boolean;
|
|
94
|
+
|
|
95
|
+
@property({ type: String })
|
|
96
|
+
icon: string;
|
|
97
|
+
|
|
77
98
|
@property()
|
|
78
99
|
backgroundColor: string;
|
|
79
100
|
|
|
@@ -97,10 +118,12 @@ export default class Label extends LitElement {
|
|
|
97
118
|
secondary: this.secondary,
|
|
98
119
|
light: this.light,
|
|
99
120
|
dark: this.dark,
|
|
121
|
+
shadow: this.shadow,
|
|
100
122
|
})}"
|
|
101
123
|
style=${styleMap(labelStyle)}
|
|
102
124
|
>
|
|
103
125
|
<div class="mask">
|
|
126
|
+
${this.icon ? html`<temba-icon name=${this.icon} />` : null}
|
|
104
127
|
<slot></slot>
|
|
105
128
|
</div>
|
|
106
129
|
</div>
|
package/src/select/Select.ts
CHANGED
|
@@ -12,7 +12,7 @@ import '../options/Options';
|
|
|
12
12
|
import { EventHandler } from '../RapidElement';
|
|
13
13
|
import { FormElement } from '../FormElement';
|
|
14
14
|
|
|
15
|
-
import
|
|
15
|
+
import Lru from 'tiny-lru';
|
|
16
16
|
import { CompletionOption, CustomEventType, Position } from '../interfaces';
|
|
17
17
|
import {
|
|
18
18
|
renderCompletionOption,
|
|
@@ -497,7 +497,7 @@ export class Select extends FormElement {
|
|
|
497
497
|
|
|
498
498
|
private removingSelection: boolean;
|
|
499
499
|
|
|
500
|
-
private lruCache =
|
|
500
|
+
private lruCache = Lru(20, 60000);
|
|
501
501
|
|
|
502
502
|
// http promise to monitor for completeness
|
|
503
503
|
public httpComplete: Promise<void | WebResponse>;
|
|
@@ -507,7 +507,7 @@ export class Select extends FormElement {
|
|
|
507
507
|
|
|
508
508
|
// if our cache key changes, clear it out
|
|
509
509
|
if (changedProperties.has('cacheKey')) {
|
|
510
|
-
this.lruCache.clear(
|
|
510
|
+
this.lruCache.clear();
|
|
511
511
|
}
|
|
512
512
|
|
|
513
513
|
if (
|
|
@@ -594,7 +594,7 @@ export class Select extends FormElement {
|
|
|
594
594
|
postJSON(this.endpoint, selected).then(response => {
|
|
595
595
|
if (response.status >= 200 && response.status < 300) {
|
|
596
596
|
this.setSelectedOption(response.json);
|
|
597
|
-
this.lruCache =
|
|
597
|
+
this.lruCache = Lru(20, 60000);
|
|
598
598
|
} else {
|
|
599
599
|
// TODO: find a way to share inline errors
|
|
600
600
|
this.blur();
|
package/src/store/Store.ts
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import { LitElement } from 'lit';
|
|
2
1
|
import { property } from 'lit/decorators';
|
|
3
|
-
import { getUrl, getAssets, Asset, WebResponse } from '../utils';
|
|
2
|
+
import { fetchResults, getUrl, getAssets, Asset, WebResponse } from '../utils';
|
|
4
3
|
import {
|
|
5
4
|
ContactField,
|
|
6
5
|
ContactGroup,
|
|
7
6
|
CompletionOption,
|
|
8
7
|
CompletionSchema,
|
|
9
8
|
KeyedAssets,
|
|
9
|
+
CustomEventType,
|
|
10
10
|
} from '../interfaces';
|
|
11
|
+
import { RapidElement } from '../RapidElement';
|
|
12
|
+
import Lru from 'tiny-lru';
|
|
13
|
+
|
|
14
|
+
export class Store extends RapidElement {
|
|
15
|
+
@property({ type: Number })
|
|
16
|
+
ttl = 60000;
|
|
17
|
+
|
|
18
|
+
@property({ type: Number })
|
|
19
|
+
max = 20;
|
|
11
20
|
|
|
12
|
-
export class Store extends LitElement {
|
|
13
21
|
@property({ type: String, attribute: 'completion' })
|
|
14
22
|
completionEndpoint: string;
|
|
15
23
|
|
|
@@ -34,10 +42,16 @@ export class Store extends LitElement {
|
|
|
34
42
|
private fields: { [key: string]: ContactField } = {};
|
|
35
43
|
private groups: { [uuid: string]: ContactGroup } = {};
|
|
36
44
|
|
|
45
|
+
private pinnedFields: ContactField[] = [];
|
|
46
|
+
|
|
37
47
|
// http promise to monitor for completeness
|
|
38
48
|
public httpComplete: Promise<void | WebResponse[]>;
|
|
39
49
|
|
|
50
|
+
private cache: any;
|
|
51
|
+
|
|
40
52
|
public firstUpdated() {
|
|
53
|
+
this.cache = Lru(this.max, this.ttl);
|
|
54
|
+
|
|
41
55
|
const fetches = [];
|
|
42
56
|
if (this.completionEndpoint) {
|
|
43
57
|
fetches.push(
|
|
@@ -52,9 +66,18 @@ export class Store extends LitElement {
|
|
|
52
66
|
fetches.push(
|
|
53
67
|
getAssets(this.fieldsEndpoint).then((assets: Asset[]) => {
|
|
54
68
|
this.keyedAssets['fields'] = [];
|
|
69
|
+
this.pinnedFields = [];
|
|
70
|
+
|
|
55
71
|
assets.forEach((field: ContactField) => {
|
|
56
72
|
this.keyedAssets['fields'].push(field.key);
|
|
57
73
|
this.fields[field.key] = field;
|
|
74
|
+
if (field.pinned) {
|
|
75
|
+
this.pinnedFields.push(field);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
this.pinnedFields.sort((a, b) => {
|
|
80
|
+
return b.priority - a.priority;
|
|
58
81
|
});
|
|
59
82
|
})
|
|
60
83
|
);
|
|
@@ -105,6 +128,10 @@ export class Store extends LitElement {
|
|
|
105
128
|
return this.fields[key];
|
|
106
129
|
}
|
|
107
130
|
|
|
131
|
+
public getPinnedFields(): ContactField[] {
|
|
132
|
+
return this.pinnedFields;
|
|
133
|
+
}
|
|
134
|
+
|
|
108
135
|
public isDynamicGroup(uuid: string): boolean {
|
|
109
136
|
const group = this.groups[uuid];
|
|
110
137
|
if (group && group.query) {
|
|
@@ -112,4 +139,98 @@ export class Store extends LitElement {
|
|
|
112
139
|
}
|
|
113
140
|
return false;
|
|
114
141
|
}
|
|
142
|
+
|
|
143
|
+
public getUrl(
|
|
144
|
+
url: string,
|
|
145
|
+
options?: {
|
|
146
|
+
force?: boolean;
|
|
147
|
+
controller?: AbortController;
|
|
148
|
+
headers?: { [key: string]: string };
|
|
149
|
+
}
|
|
150
|
+
): Promise<WebResponse> {
|
|
151
|
+
options = options || {};
|
|
152
|
+
if (!options.force && this.cache.has(url)) {
|
|
153
|
+
return new Promise<WebResponse>(resolve => {
|
|
154
|
+
resolve(this.cache.get(url));
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return getUrl(url, options.controller, options.headers || {}).then(
|
|
159
|
+
(response: WebResponse) => {
|
|
160
|
+
return new Promise<WebResponse>((resolve, reject) => {
|
|
161
|
+
if (response.status >= 200 && response.status <= 300) {
|
|
162
|
+
this.cache.set(url, response);
|
|
163
|
+
resolve(response);
|
|
164
|
+
} else {
|
|
165
|
+
reject('Status: ' + response.status);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private pendingResolves = {};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Fetches all of the results for a given API endpoint with caching
|
|
176
|
+
* @param url
|
|
177
|
+
*/
|
|
178
|
+
public getResults(
|
|
179
|
+
url: string,
|
|
180
|
+
options?: { force?: boolean }
|
|
181
|
+
): Promise<any[]> {
|
|
182
|
+
options = options || {};
|
|
183
|
+
const key = 'results_' + url;
|
|
184
|
+
const results = this.cache.get(key);
|
|
185
|
+
|
|
186
|
+
if (!options.force && results) {
|
|
187
|
+
return new Promise<any[]>(resolve => {
|
|
188
|
+
resolve(results);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return new Promise<any[]>(resolve => {
|
|
193
|
+
const pending = this.pendingResolves[url] || [];
|
|
194
|
+
pending.push(resolve);
|
|
195
|
+
this.pendingResolves[url] = pending;
|
|
196
|
+
if (pending.length <= 1) {
|
|
197
|
+
fetchResults(url).then((results: any[]) => {
|
|
198
|
+
this.cache.set(key, results);
|
|
199
|
+
const pending = this.pendingResolves[url] || [];
|
|
200
|
+
while (pending.length > 0) {
|
|
201
|
+
const resolve = pending.pop();
|
|
202
|
+
resolve(results);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public fetching: { [url: string]: number } = {};
|
|
210
|
+
|
|
211
|
+
public makeRequest(
|
|
212
|
+
url: string,
|
|
213
|
+
options?: { force?: boolean; prepareData?: (data: any) => any }
|
|
214
|
+
) {
|
|
215
|
+
const previousRequest = this.fetching[url];
|
|
216
|
+
const now = new Date().getTime();
|
|
217
|
+
// if the request was recently made, don't do anything
|
|
218
|
+
if (previousRequest && now - previousRequest < 500) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
this.fetching[url] = now;
|
|
223
|
+
options = options || {};
|
|
224
|
+
const cached = this.cache.get(url);
|
|
225
|
+
if (cached && !options.force) {
|
|
226
|
+
this.fireCustomEvent(CustomEventType.StoreUpdated, { url, data: cached });
|
|
227
|
+
} else {
|
|
228
|
+
fetchResults(url).then(data => {
|
|
229
|
+
data = options.prepareData ? options.prepareData(data) : data;
|
|
230
|
+
this.cache.set(url, data);
|
|
231
|
+
this.fireCustomEvent(CustomEventType.StoreUpdated, { url, data });
|
|
232
|
+
delete this.fetching[url];
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
115
236
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { PropertyValueMap } from 'lit';
|
|
2
|
+
import { property } from 'lit/decorators';
|
|
3
|
+
import { CustomEventType } from '../interfaces';
|
|
4
|
+
import { RapidElement } from '../RapidElement';
|
|
5
|
+
import { Store } from './Store';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* StoreElement is a listener for a given endpoint that re-renders
|
|
9
|
+
* when the underlying store element changes
|
|
10
|
+
*/
|
|
11
|
+
export class StoreElement extends RapidElement {
|
|
12
|
+
@property({ type: String })
|
|
13
|
+
url: string;
|
|
14
|
+
|
|
15
|
+
@property({ type: Object, attribute: false })
|
|
16
|
+
data: any;
|
|
17
|
+
|
|
18
|
+
store: Store;
|
|
19
|
+
|
|
20
|
+
prepareData(data: any): any {
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public refresh() {
|
|
25
|
+
this.store.makeRequest(this.url, {
|
|
26
|
+
prepareData: this.prepareData,
|
|
27
|
+
force: true,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private handleStoreUpdated(event: CustomEvent) {
|
|
32
|
+
if (event.detail.url === this.url) {
|
|
33
|
+
this.data = event.detail.data;
|
|
34
|
+
this.fireCustomEvent(CustomEventType.Refreshed, { data: this.data });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected updated(
|
|
39
|
+
properties: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
|
40
|
+
): void {
|
|
41
|
+
super.updated(properties);
|
|
42
|
+
if (properties.has('url')) {
|
|
43
|
+
if (this.url) {
|
|
44
|
+
this.store.makeRequest(this.url, { prepareData: this.prepareData });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
connectedCallback(): void {
|
|
50
|
+
super.connectedCallback();
|
|
51
|
+
this.store = document.querySelector('temba-store') as Store;
|
|
52
|
+
this.handleStoreUpdated = this.handleStoreUpdated.bind(this);
|
|
53
|
+
this.prepareData = this.prepareData.bind(this);
|
|
54
|
+
if (this.store) {
|
|
55
|
+
this.store.addEventListener(
|
|
56
|
+
CustomEventType.StoreUpdated,
|
|
57
|
+
this.handleStoreUpdated
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
disconnectedCallback(): void {
|
|
63
|
+
super.disconnectedCallback();
|
|
64
|
+
if (this.store) {
|
|
65
|
+
this.store.removeEventListener(
|
|
66
|
+
CustomEventType.StoreUpdated,
|
|
67
|
+
this.handleStoreUpdated
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -186,6 +186,8 @@ export class TextInput extends FormElement {
|
|
|
186
186
|
cursorStart = -1;
|
|
187
187
|
cursorEnd = -1;
|
|
188
188
|
|
|
189
|
+
isoFormattedDate: string;
|
|
190
|
+
|
|
189
191
|
public constructor() {
|
|
190
192
|
super();
|
|
191
193
|
}
|
|
@@ -197,6 +199,7 @@ export class TextInput extends FormElement {
|
|
|
197
199
|
if (this.dateElement) {
|
|
198
200
|
this.onDateUpdated = this.onDateUpdated.bind(this);
|
|
199
201
|
this.onDateReady = this.onDateReady.bind(this);
|
|
202
|
+
this.onDateClose = this.onDateClose.bind(this);
|
|
200
203
|
}
|
|
201
204
|
|
|
202
205
|
this.inputElement = this.shadowRoot.querySelector('.textinput');
|
|
@@ -216,51 +219,67 @@ export class TextInput extends FormElement {
|
|
|
216
219
|
|
|
217
220
|
public updated(changes: Map<string, any>) {
|
|
218
221
|
super.updated(changes);
|
|
219
|
-
if (changes.has('value')) {
|
|
220
|
-
this.setValues([this.value]);
|
|
221
|
-
this.fireEvent('change');
|
|
222
|
-
|
|
223
|
-
if (this.textarea && this.autogrow) {
|
|
224
|
-
const autogrow = this.shadowRoot.querySelector(
|
|
225
|
-
'.grow-wrap > div'
|
|
226
|
-
) as HTMLDivElement;
|
|
227
|
-
autogrow.innerText = this.value + String.fromCharCode(10);
|
|
228
|
-
}
|
|
229
222
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
this.
|
|
233
|
-
|
|
223
|
+
if (changes.has('value')) {
|
|
224
|
+
if (this.datepicker || this.datetimepicker) {
|
|
225
|
+
this.onDateReady();
|
|
226
|
+
} else {
|
|
227
|
+
this.setValues([this.value]);
|
|
228
|
+
this.fireEvent('change');
|
|
229
|
+
|
|
230
|
+
if (this.textarea && this.autogrow) {
|
|
231
|
+
const autogrow = this.shadowRoot.querySelector(
|
|
232
|
+
'.grow-wrap > div'
|
|
233
|
+
) as HTMLDivElement;
|
|
234
|
+
autogrow.innerText = this.value + String.fromCharCode(10);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (this.cursorStart > -1 && this.cursorEnd > -1) {
|
|
238
|
+
this.inputElement.setSelectionRange(this.cursorStart, this.cursorEnd);
|
|
239
|
+
this.cursorStart = -1;
|
|
240
|
+
this.cursorEnd = -1;
|
|
241
|
+
}
|
|
234
242
|
}
|
|
235
243
|
}
|
|
236
244
|
}
|
|
237
245
|
|
|
238
|
-
private onDateUpdated(dates: Date[],
|
|
246
|
+
private onDateUpdated(dates: Date[], isoFormatDate: string) {
|
|
239
247
|
if (dates.length > 0) {
|
|
248
|
+
this.isoFormattedDate = isoFormatDate;
|
|
240
249
|
this.inputElement.value = this.dateElement.formatDate(
|
|
241
250
|
dates[0],
|
|
242
251
|
this.dateElement.altFormat
|
|
243
252
|
);
|
|
244
|
-
|
|
245
|
-
this.setValue(formattedDate);
|
|
246
|
-
this.inputElement.blur();
|
|
247
253
|
}
|
|
248
254
|
}
|
|
249
255
|
|
|
256
|
+
public getDisplayValue() {
|
|
257
|
+
return this.inputElement.value;
|
|
258
|
+
}
|
|
259
|
+
|
|
250
260
|
private onDateReady() {
|
|
251
261
|
window.setTimeout(() => {
|
|
252
|
-
if (this.
|
|
253
|
-
this.
|
|
254
|
-
this.dateElement.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
262
|
+
if (this.dateElement) {
|
|
263
|
+
if (this.value) {
|
|
264
|
+
this.inputElement.value = this.dateElement.formatDate(
|
|
265
|
+
this.dateElement.parseDate(this.value),
|
|
266
|
+
this.dateElement.altFormat
|
|
267
|
+
);
|
|
268
|
+
this.dateElement.setDate(this.value);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this.loading = false;
|
|
258
272
|
}
|
|
259
|
-
|
|
260
|
-
this.loading = false;
|
|
261
273
|
}, 0);
|
|
262
274
|
}
|
|
263
275
|
|
|
276
|
+
private onDateClose() {
|
|
277
|
+
if (this.isoFormattedDate) {
|
|
278
|
+
this.setValue(this.isoFormattedDate);
|
|
279
|
+
this.fireEvent('blur');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
264
283
|
private handleClear(event: any): void {
|
|
265
284
|
event.stopPropagation();
|
|
266
285
|
event.preventDefault();
|
|
@@ -502,9 +521,10 @@ export class TextInput extends FormElement {
|
|
|
502
521
|
class="datepicker hidden"
|
|
503
522
|
altInput
|
|
504
523
|
altFormat="${this.datepicker ? 'F j, Y' : 'F j, Y h:i K'}"
|
|
505
|
-
dateFormat="${this.datepicker ? 'Y-m-d' : '
|
|
524
|
+
dateFormat="${this.datepicker ? 'Y-m-d' : 'Z'}"
|
|
506
525
|
.onValueUpdate=${this.onDateUpdated}
|
|
507
526
|
.onReady=${this.onDateReady}
|
|
527
|
+
.onClose=${this.onDateClose}
|
|
508
528
|
?enableTime=${this.datetimepicker}
|
|
509
529
|
></lit-flatpickr>
|
|
510
530
|
`;
|
|
@@ -525,6 +545,7 @@ export class TextInput extends FormElement {
|
|
|
525
545
|
style=${styleMap(containerStyle)}
|
|
526
546
|
@click=${this.handleContainerClick}
|
|
527
547
|
>
|
|
548
|
+
<slot name="prefix"></slot>
|
|
528
549
|
${input} ${clear}
|
|
529
550
|
<slot></slot>
|
|
530
551
|
</div>
|
|
@@ -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 = 8;
|
|
8
8
|
|
|
9
9
|
export class VectorIcon extends LitElement {
|
|
10
10
|
@property({ type: String })
|
|
@@ -32,6 +32,9 @@ export class VectorIcon extends LitElement {
|
|
|
32
32
|
@property({ type: Number })
|
|
33
33
|
animationDuration = 200;
|
|
34
34
|
|
|
35
|
+
@property({ type: String })
|
|
36
|
+
href = '';
|
|
37
|
+
|
|
35
38
|
@property({ type: Number, attribute: false })
|
|
36
39
|
steps = 2;
|
|
37
40
|
|
|
@@ -53,7 +56,7 @@ export class VectorIcon extends LitElement {
|
|
|
53
56
|
padding-bottom: 0.2em;
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
.sheet {
|
|
57
60
|
fill: var(--icon-color);
|
|
58
61
|
transform: scale(1);
|
|
59
62
|
transition: fill 100ms ease-in-out,
|
|
@@ -62,28 +65,28 @@ export class VectorIcon extends LitElement {
|
|
|
62
65
|
margin 200ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
.sheet.spin {
|
|
66
69
|
transform: rotate(0deg);
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
|
|
72
|
+
.sheet.spin-1 {
|
|
70
73
|
transform: rotate(180deg);
|
|
71
74
|
}
|
|
72
75
|
|
|
73
|
-
|
|
76
|
+
.sheet.spin-2 {
|
|
74
77
|
transform: rotate(360deg);
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
|
|
80
|
+
.sheet.spin-3 {
|
|
78
81
|
transform: rotate(0deg);
|
|
79
82
|
transition-duration: 0ms !important;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
|
|
85
|
+
.sheet.pulse {
|
|
83
86
|
transform: scale(1);
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
|
|
89
|
+
.sheet.pulse-1 {
|
|
87
90
|
transform: scale(1.2);
|
|
88
91
|
}
|
|
89
92
|
|
|
@@ -192,18 +195,20 @@ export class VectorIcon extends LitElement {
|
|
|
192
195
|
this.steps}ms
|
|
193
196
|
${this.easing}"
|
|
194
197
|
class="${getClasses({
|
|
198
|
+
sheet: this.href === '',
|
|
195
199
|
[this.animateChange]: !!this.animateChange,
|
|
196
200
|
[this.animateChange + '-' + this.animationStep]:
|
|
197
201
|
this.animationStep > 0,
|
|
198
202
|
})}"
|
|
199
203
|
>
|
|
200
204
|
<use
|
|
201
|
-
href="${this.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
href="${this.href
|
|
206
|
+
? this.href
|
|
207
|
+
: `${
|
|
208
|
+
this.prefix || (window as any).static_url || '/static/'
|
|
209
|
+
}icons/symbol-defs.svg?v=${ICON_VERSION}#icon-${
|
|
210
|
+
this.lastName || this.name || this.id
|
|
211
|
+
}`}"
|
|
207
212
|
/>
|
|
208
213
|
</svg>
|
|
209
214
|
</div>
|