@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.
Files changed (98) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/demo/index.html +9 -1
  3. package/dist/{d08b61e5.js → d0cc86be.js} +386 -38
  4. package/dist/index.js +386 -38
  5. package/dist/static/icons/symbol-defs.svg +13 -1
  6. package/dist/static/img/schemes/email.svg +1 -0
  7. package/dist/static/img/schemes/facebook.svg +1 -0
  8. package/dist/static/img/schemes/instagram.svg +1 -0
  9. package/dist/static/img/schemes/line.svg +1 -0
  10. package/dist/static/img/schemes/messenger.svg +1 -0
  11. package/dist/static/img/schemes/tel.svg +34 -0
  12. package/dist/static/img/schemes/telegram.svg +1 -0
  13. package/dist/static/img/schemes/twitter.svg +1 -0
  14. package/dist/static/img/schemes/viber.svg +1 -0
  15. package/dist/static/img/schemes/vk.svg +1 -0
  16. package/dist/static/img/schemes/whatsapp.svg +1 -0
  17. package/dist/sw.js +1 -1
  18. package/dist/sw.js.map +1 -1
  19. package/dist/templates/components-body.html +1 -1
  20. package/dist/templates/components-head.html +1 -1
  21. package/out-tsc/src/RapidElement.js.map +1 -1
  22. package/out-tsc/src/RefreshElement.js +28 -0
  23. package/out-tsc/src/RefreshElement.js.map +1 -0
  24. package/out-tsc/src/button/Button.js +4 -0
  25. package/out-tsc/src/button/Button.js.map +1 -1
  26. package/out-tsc/src/contacts/ContactChat.js +5 -13
  27. package/out-tsc/src/contacts/ContactChat.js.map +1 -1
  28. package/out-tsc/src/contacts/ContactFieldEditor.js +199 -0
  29. package/out-tsc/src/contacts/ContactFieldEditor.js.map +1 -0
  30. package/out-tsc/src/contacts/ContactFields.js +106 -0
  31. package/out-tsc/src/contacts/ContactFields.js.map +1 -0
  32. package/out-tsc/src/contacts/ContactGroups.js +39 -0
  33. package/out-tsc/src/contacts/ContactGroups.js.map +1 -0
  34. package/out-tsc/src/contacts/ContactName.js +40 -0
  35. package/out-tsc/src/contacts/ContactName.js.map +1 -0
  36. package/out-tsc/src/contacts/ContactStoreElement.js +44 -0
  37. package/out-tsc/src/contacts/ContactStoreElement.js.map +1 -0
  38. package/out-tsc/src/contacts/ContactUrn.js +38 -0
  39. package/out-tsc/src/contacts/ContactUrn.js.map +1 -0
  40. package/out-tsc/src/contacts/events.js +42 -4
  41. package/out-tsc/src/contacts/events.js.map +1 -1
  42. package/out-tsc/src/interfaces.js +1 -0
  43. package/out-tsc/src/interfaces.js.map +1 -1
  44. package/out-tsc/src/label/Label.js +32 -12
  45. package/out-tsc/src/label/Label.js.map +1 -1
  46. package/out-tsc/src/select/Select.js +4 -4
  47. package/out-tsc/src/select/Select.js.map +1 -1
  48. package/out-tsc/src/store/Store.js +97 -3
  49. package/out-tsc/src/store/Store.js.map +1 -1
  50. package/out-tsc/src/store/StoreElement.js +55 -0
  51. package/out-tsc/src/store/StoreElement.js.map +1 -0
  52. package/out-tsc/src/textinput/TextInput.js +35 -17
  53. package/out-tsc/src/textinput/TextInput.js.map +1 -1
  54. package/out-tsc/src/vectoricon/VectorIcon.js +16 -14
  55. package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
  56. package/out-tsc/temba-modules.js +12 -0
  57. package/out-tsc/temba-modules.js.map +1 -1
  58. package/package.json +3 -3
  59. package/rollup.config.js +1 -0
  60. package/src/RapidElement.ts +0 -1
  61. package/src/RefreshElement.ts +33 -0
  62. package/src/button/Button.ts +4 -0
  63. package/src/contacts/ContactChat.ts +7 -16
  64. package/src/contacts/ContactFieldEditor.ts +201 -0
  65. package/src/contacts/ContactFields.ts +112 -0
  66. package/src/contacts/ContactGroups.ts +41 -0
  67. package/src/contacts/ContactName.ts +37 -0
  68. package/src/contacts/ContactStoreElement.ts +51 -0
  69. package/src/contacts/ContactUrn.ts +38 -0
  70. package/src/contacts/events.ts +41 -4
  71. package/src/interfaces.ts +2 -0
  72. package/src/label/Label.ts +30 -7
  73. package/src/select/Select.ts +4 -4
  74. package/src/store/Store.ts +124 -3
  75. package/src/store/StoreElement.ts +71 -0
  76. package/src/textinput/TextInput.ts +48 -27
  77. package/src/vectoricon/VectorIcon.ts +19 -14
  78. package/static/icons/Read Me.txt +1 -1
  79. package/static/icons/SVG/calendar1.svg +5 -0
  80. package/static/icons/SVG/corner-down-left.svg +5 -0
  81. package/static/icons/SVG/more-horizontal.svg +5 -0
  82. package/static/icons/SVG/refresh-cw.svg +5 -0
  83. package/static/icons/demo-external-svg.html +21 -1
  84. package/static/icons/demo.html +34 -2
  85. package/static/icons/selection.json +412 -316
  86. package/static/icons/symbol-defs.svg +13 -1
  87. package/static/img/schemes/email.svg +1 -0
  88. package/static/img/schemes/facebook.svg +1 -0
  89. package/static/img/schemes/instagram.svg +1 -0
  90. package/static/img/schemes/line.svg +1 -0
  91. package/static/img/schemes/messenger.svg +1 -0
  92. package/static/img/schemes/tel.svg +34 -0
  93. package/static/img/schemes/telegram.svg +1 -0
  94. package/static/img/schemes/twitter.svg +1 -0
  95. package/static/img/schemes/viber.svg +1 -0
  96. package/static/img/schemes/vk.svg +1 -0
  97. package/static/img/schemes/whatsapp.svg +1 -0
  98. package/temba-modules.ts +12 -0
@@ -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
- style="width:100%;height:300px;border-radius:var(--curvature);box-shadow:0px 0px 10px -1px rgb(160 160 160);overflow:hidden"
678
- ><embed src="${url}#view=Fit" type="application/pdf" frameBorder="0" scrolling="auto" height="100%" width="100%"></embed></div>`;
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 max-width="400px" height="auto" controls="controls">
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&amp;layer=mapnik&amp;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 10px -1px rgb(160 160 160);overflow:hidden"
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
  }
@@ -1,9 +1,8 @@
1
1
  import { LitElement, TemplateResult, html, css } from 'lit';
2
- import { property, customElement } from 'lit/decorators';
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 6px;
16
- border-radius: var(--curvature);
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
- border-radius: 2px;
25
- font-size: 80%;
32
+ font-size: 0.8em;
26
33
  font-weight: 400;
27
- border-radius: var(--curvature);
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>
@@ -12,7 +12,7 @@ import '../options/Options';
12
12
  import { EventHandler } from '../RapidElement';
13
13
  import { FormElement } from '../FormElement';
14
14
 
15
- import flru from 'flru';
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 = flru(20);
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(false);
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 = flru(20);
597
+ this.lruCache = Lru(20, 60000);
598
598
  } else {
599
599
  // TODO: find a way to share inline errors
600
600
  this.blur();
@@ -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
- if (this.cursorStart > -1 && this.cursorEnd > -1) {
231
- this.inputElement.setSelectionRange(this.cursorStart, this.cursorEnd);
232
- this.cursorStart = -1;
233
- this.cursorEnd = -1;
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[], formattedDate: string) {
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.value) {
253
- this.inputElement.value = this.dateElement.formatDate(
254
- this.dateElement.parseDate(this.value),
255
- this.dateElement.altFormat
256
- );
257
- this.dateElement.setDate(this.value);
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' : 'Y-m-d H:i'}"
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 = 6;
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
- svg {
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
- svg.spin {
68
+ .sheet.spin {
66
69
  transform: rotate(0deg);
67
70
  }
68
71
 
69
- svg.spin-1 {
72
+ .sheet.spin-1 {
70
73
  transform: rotate(180deg);
71
74
  }
72
75
 
73
- svg.spin-2 {
76
+ .sheet.spin-2 {
74
77
  transform: rotate(360deg);
75
78
  }
76
79
 
77
- svg.spin-3 {
80
+ .sheet.spin-3 {
78
81
  transform: rotate(0deg);
79
82
  transition-duration: 0ms !important;
80
83
  }
81
84
 
82
- svg.pulse {
85
+ .sheet.pulse {
83
86
  transform: scale(1);
84
87
  }
85
88
 
86
- svg.pulse-1 {
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.prefix ||
202
- (window as any).static_url ||
203
- '/static/'}icons/symbol-defs.svg?v=${ICON_VERSION}#icon-${this
204
- .lastName ||
205
- this.name ||
206
- this.id}"
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>