@nyaruka/temba-components 0.21.0 → 0.24.1

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 (79) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/85f2e730.js +356 -0
  3. package/dist/index.js +356 -1
  4. package/dist/static/icons/symbol-defs.svg +53 -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/anchor/Anchor.js +25 -0
  10. package/out-tsc/src/anchor/Anchor.js.map +1 -0
  11. package/out-tsc/src/checkbox/Checkbox.js +29 -14
  12. package/out-tsc/src/checkbox/Checkbox.js.map +1 -1
  13. package/out-tsc/src/contacts/ContactDetails.js +9 -4
  14. package/out-tsc/src/contacts/ContactDetails.js.map +1 -1
  15. package/out-tsc/src/contacts/events.js +33 -7
  16. package/out-tsc/src/contacts/events.js.map +1 -1
  17. package/out-tsc/src/contactsearch/ContactSearch.js +146 -72
  18. package/out-tsc/src/contactsearch/ContactSearch.js.map +1 -1
  19. package/out-tsc/src/dialog/Dialog.js +11 -2
  20. package/out-tsc/src/dialog/Dialog.js.map +1 -1
  21. package/out-tsc/src/dialog/Modax.js +23 -4
  22. package/out-tsc/src/dialog/Modax.js.map +1 -1
  23. package/out-tsc/src/interfaces.js +1 -0
  24. package/out-tsc/src/interfaces.js.map +1 -1
  25. package/out-tsc/src/list/TembaMenu.js +139 -39
  26. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  27. package/out-tsc/src/omnibox/Omnibox.js +7 -1
  28. package/out-tsc/src/omnibox/Omnibox.js.map +1 -1
  29. package/out-tsc/src/select/Select.js +7 -1
  30. package/out-tsc/src/select/Select.js.map +1 -1
  31. package/out-tsc/src/textinput/TextInput.js +42 -1
  32. package/out-tsc/src/textinput/TextInput.js.map +1 -1
  33. package/out-tsc/src/utils/index.js +13 -14
  34. package/out-tsc/src/utils/index.js.map +1 -1
  35. package/out-tsc/src/vectoricon/VectorIcon.js +2 -1
  36. package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
  37. package/out-tsc/temba-modules.js +2 -0
  38. package/out-tsc/temba-modules.js.map +1 -1
  39. package/package.json +1 -1
  40. package/screenshots/truth/checkbox/checked.png +0 -0
  41. package/screenshots/truth/checkbox/default.png +0 -0
  42. package/screenshots/truth/contacts/history-expanded.png +0 -0
  43. package/screenshots/truth/list/menu-submenu.png +0 -0
  44. package/src/anchor/Anchor.ts +26 -0
  45. package/src/checkbox/Checkbox.ts +31 -16
  46. package/src/contacts/ContactDetails.ts +9 -4
  47. package/src/contacts/events.ts +33 -7
  48. package/src/contactsearch/ContactSearch.ts +157 -80
  49. package/src/dialog/Dialog.ts +12 -2
  50. package/src/dialog/Modax.ts +20 -4
  51. package/src/interfaces.ts +1 -0
  52. package/src/list/TembaMenu.ts +158 -42
  53. package/src/omnibox/Omnibox.ts +9 -1
  54. package/src/select/Select.ts +9 -1
  55. package/src/textinput/TextInput.ts +47 -1
  56. package/src/utils/index.ts +17 -16
  57. package/src/vectoricon/VectorIcon.ts +2 -1
  58. package/static/icons/Read Me.txt +1 -1
  59. package/static/icons/SVG/channel.svg +5 -0
  60. package/static/icons/SVG/cloud1.svg +5 -0
  61. package/static/icons/SVG/codepen.svg +5 -0
  62. package/static/icons/SVG/codesandbox.svg +5 -0
  63. package/static/icons/SVG/git-pull-request.svg +5 -0
  64. package/static/icons/SVG/grid.svg +5 -0
  65. package/static/icons/SVG/hard-drive.svg +5 -0
  66. package/static/icons/SVG/layout.svg +5 -0
  67. package/static/icons/SVG/list.svg +5 -0
  68. package/static/icons/SVG/map-pin.svg +5 -0
  69. package/static/icons/SVG/package.svg +5 -0
  70. package/static/icons/SVG/zapier.svg +5 -0
  71. package/static/icons/demo-external-svg.html +228 -173
  72. package/static/icons/demo-files/demo.css +4 -4
  73. package/static/icons/demo.html +281 -193
  74. package/static/icons/selection.json +625 -348
  75. package/static/icons/style.css +4 -4
  76. package/static/icons/symbol-defs.svg +53 -20
  77. package/temba-modules.ts +2 -0
  78. package/dist/b708bdad.js +0 -1
  79. package/static/icons/SVG/zendesk1.svg +0 -5
@@ -1,8 +1,8 @@
1
1
  import { TemplateResult, html, property, css } from 'lit-element';
2
- import { getUrl, fillTemplate, WebResponse } from '../utils';
2
+ import { getUrl, WebResponse } from '../utils';
3
3
  import { TextInput } from '../textinput/TextInput';
4
4
  import '../alert/Alert';
5
- import { Contact } from '../interfaces';
5
+ import { Contact, CustomEventType } from '../interfaces';
6
6
  import { styleMap } from 'lit-html/directives/style-map';
7
7
  import { FormElement } from '../FormElement';
8
8
 
@@ -31,7 +31,7 @@ export class ContactSearch extends FormElement {
31
31
  width: 160px;
32
32
  }
33
33
 
34
- .created-on {
34
+ .date {
35
35
  text-align: right;
36
36
  }
37
37
 
@@ -40,7 +40,7 @@ export class ContactSearch extends FormElement {
40
40
  color: var(--color-text-dark);
41
41
  }
42
42
 
43
- .field-header.created-on {
43
+ .field-header.date {
44
44
  text-align: right;
45
45
  }
46
46
 
@@ -55,12 +55,6 @@ export class ContactSearch extends FormElement {
55
55
 
56
56
  table {
57
57
  width: 100%;
58
- padding-top: 10px;
59
- }
60
-
61
- .header td {
62
- border-bottom: 2px solid var(--color-borders);
63
- padding: 5px 3px;
64
58
  }
65
59
 
66
60
  .contact td {
@@ -92,6 +86,46 @@ export class ContactSearch extends FormElement {
92
86
  .error {
93
87
  margin-top: 10px;
94
88
  }
89
+
90
+ .match-count {
91
+ padding: 4px;
92
+ margin-top: 6px;
93
+ }
94
+
95
+ .linked {
96
+ color: var(--color-link-primary);
97
+ text-decoration: none;
98
+ cursor: pointer;
99
+ }
100
+
101
+ .header td {
102
+ border-bottom: 0px solid var(--color-borders);
103
+ padding: 5px 3px;
104
+ }
105
+
106
+ .expanded .header td {
107
+ border-bottom: 2px solid var(--color-borders);
108
+ }
109
+
110
+ td.field-header,
111
+ tr.table-footer,
112
+ tr.contact {
113
+ display: none;
114
+ }
115
+
116
+ .expanded td.field-header {
117
+ display: table-cell;
118
+ }
119
+
120
+ .expanded tr.contact,
121
+ .expanded tr.table-footer {
122
+ display: table-row;
123
+ }
124
+
125
+ .query {
126
+ display: var(--contact-search-query-display);
127
+ margin-bottom: 10px;
128
+ }
95
129
  `;
96
130
  }
97
131
 
@@ -100,6 +134,9 @@ export class ContactSearch extends FormElement {
100
134
  @property({ type: Boolean })
101
135
  fetching: boolean;
102
136
 
137
+ @property({ type: Boolean })
138
+ expanded: boolean;
139
+
103
140
  @property({ type: String })
104
141
  endpoint: string;
105
142
 
@@ -112,8 +149,11 @@ export class ContactSearch extends FormElement {
112
149
  @property({ type: String })
113
150
  query = '';
114
151
 
115
- @property({ type: String, attribute: 'matches-text' })
116
- matchesText = '';
152
+ @property({ type: Number })
153
+ inactiveThreshold = 1000;
154
+
155
+ @property({ type: Number })
156
+ inactiveDays = 90;
117
157
 
118
158
  @property({ attribute: false })
119
159
  summary: SummaryResponse;
@@ -140,16 +180,22 @@ export class ContactSearch extends FormElement {
140
180
  }
141
181
  }
142
182
 
143
- public fetchSummary(query: string): any {
144
- // const CancelToken = axios.CancelToken;
145
- // this.cancelToken = CancelToken.source();
146
-
147
- const url = this.endpoint + query;
183
+ public executeQuery(query: string): any {
184
+ const url = this.endpoint + query.replace('\n', ' ');
185
+ getUrl(url).then((response: WebResponse) => {
186
+ if (response.status === 200) {
187
+ const summary = response.json as SummaryResponse;
188
+ this.fireCustomEvent(CustomEventType.FetchComplete, summary);
189
+ }
190
+ });
191
+ }
148
192
 
193
+ public fetchSummary(query: string): any {
194
+ const url = this.endpoint + query.replace('\n', ' ');
149
195
  getUrl(url).then((response: WebResponse) => {
196
+ this.fetching = false;
150
197
  if (response.status === 200) {
151
198
  this.summary = response.json as SummaryResponse;
152
- this.fetching = false;
153
199
 
154
200
  if (this.summary.error) {
155
201
  this.errors = [this.summary.error];
@@ -157,6 +203,7 @@ export class ContactSearch extends FormElement {
157
203
  this.errors = [];
158
204
  }
159
205
  this.requestUpdate('errors');
206
+ this.fireCustomEvent(CustomEventType.ContentChanged, this.summary);
160
207
  }
161
208
  });
162
209
  }
@@ -177,54 +224,76 @@ export class ContactSearch extends FormElement {
177
224
 
178
225
  if (!this.summary.error) {
179
226
  const count = this.summary.total;
180
- const message = fillTemplate(this.matchesText, {
181
- query: this.summary.query,
182
- count,
183
- });
227
+ const lastSeenOn = this.summary.query.indexOf('last_seen_on') > -1;
184
228
 
185
229
  summary = html`
186
- <table cellspacing="0" cellpadding="0">
187
- <tr class="header">
188
- <td colspan="2"></td>
189
- ${fields.map(
190
- field => html` <td class="field-header">${field.label}</td> `
230
+ <div class="summary ${this.expanded ? 'expanded' : ''}">
231
+ <table cellspacing="0" cellpadding="0">
232
+ <tr class="header">
233
+ <td colspan="2">
234
+ Found
235
+ <a
236
+ class="linked"
237
+ target="_"
238
+ href="/contact/?search=${encodeURIComponent(
239
+ this.summary.query
240
+ )}"
241
+ >
242
+ ${count.toLocaleString()}
243
+ </a>
244
+ contact${count !== 1 ? 's' : ''}
245
+ </td>
246
+ ${fields.map(
247
+ field => html` <td class="field-header">${field.label}</td> `
248
+ )}
249
+ <td></td>
250
+ <td class="field-header date">
251
+ ${lastSeenOn ? 'Last Seen' : 'Created'}
252
+ </td>
253
+ </tr>
254
+
255
+ ${this.summary.sample.map(
256
+ (contact: Contact) => html`
257
+ <tr class="contact">
258
+ <td class="urn">
259
+ ${(contact as any).primary_urn_formatted}
260
+ </td>
261
+ <td class="name">${contact.name}</td>
262
+ ${fields.map(
263
+ field => html`
264
+ <td class="field">
265
+ ${(
266
+ (contact as any).fields[field.uuid] || { text: '' }
267
+ ).text}
268
+ </td>
269
+ `
270
+ )}
271
+ <td></td>
272
+ <td class="date">
273
+ ${lastSeenOn
274
+ ? contact.last_seen_on || '--'
275
+ : contact.created_on}
276
+ </td>
277
+ </tr>
278
+ `
191
279
  )}
192
- <td></td>
193
- <td class="field-header created-on">Created On</td>
194
- </tr>
195
-
196
- ${this.summary.sample.map(
197
- (contact: Contact) => html`
198
- <tr class="contact">
199
- <td class="urn">${(contact as any).primary_urn_formatted}</td>
200
- <td class="name">${contact.name}</td>
201
- ${fields.map(
202
- field => html`
203
- <td class="field">
204
- ${((contact as any).fields[field.uuid] || { text: '' })
205
- .text}
206
- </td>
207
- `
208
- )}
209
- <td></td>
210
- <td class="created-on">${contact.created_on}</td>
211
- </tr>
212
- `
213
- )}
214
-
215
- <tr class="table-footer">
216
- <td class="query-details" colspan=${fields.length + 3}>
217
- ${message}
218
- </td>
219
- <td class="more">
220
- ${this.summary.total > this.summary.sample.length
221
- ? html`
222
- ${this.summary.total - this.summary.sample.length} more
223
- `
224
- : null}
225
- </td>
226
- </tr>
227
- </table>
280
+ ${this.summary.total > this.summary.sample.length
281
+ ? html`<tr class="table-footer">
282
+ <td class="query-details" colspan=${fields.length + 3}></td>
283
+ <td class="more">
284
+ <a
285
+ class="linked"
286
+ target="_"
287
+ href="/contact/?search=${encodeURIComponent(
288
+ this.summary.query
289
+ )}"
290
+ >more</a
291
+ >
292
+ </td>
293
+ </tr>`
294
+ : null}
295
+ </table>
296
+ </div>
228
297
  `;
229
298
  }
230
299
  }
@@ -232,23 +301,31 @@ export class ContactSearch extends FormElement {
232
301
  const loadingStyle = this.fetching ? { opacity: '1' } : {};
233
302
 
234
303
  return html`
235
- <temba-textinput
236
- .label=${this.label}
237
- .helpText=${this.helpText}
238
- .widgetOnly=${this.widgetOnly}
239
- .errors=${this.errors}
240
- name=${this.name}
241
- .inputRoot=${this}
242
- @input=${this.handleQueryChange}
243
- placeholder=${this.placeholder}
244
- value=${this.query}
245
- >
246
- <temba-loading
247
- units="4"
248
- style=${styleMap(loadingStyle)}
249
- ></temba-loading>
250
- </temba-textinput>
251
- ${this.summary ? html` <div class="summary">${summary}</div> ` : null}
304
+ <div class="query">
305
+ <temba-textinput
306
+ .label=${this.label}
307
+ .helpText=${this.helpText}
308
+ .widgetOnly=${this.widgetOnly}
309
+ .errors=${this.errors}
310
+ name=${this.name}
311
+ .inputRoot=${this}
312
+ @input=${this.handleQueryChange}
313
+ placeholder=${this.placeholder}
314
+ .value=${this.query}
315
+ textarea
316
+ autogrow
317
+ >
318
+ </temba-textinput>
319
+ </div>
320
+
321
+ ${this.fetching
322
+ ? html`<temba-loading
323
+ units="4"
324
+ style=${styleMap(loadingStyle)}
325
+ ></temba-loading>`
326
+ : this.summary
327
+ ? html` <div class="summary">${summary}</div> `
328
+ : null}
252
329
  `;
253
330
  }
254
331
  }
@@ -68,7 +68,7 @@ export class Dialog extends RapidElement {
68
68
 
69
69
  .dialog-body {
70
70
  background: #fff;
71
- max-height: 55vh;
71
+ max-height: 75vh;
72
72
  overflow-y: auto;
73
73
  }
74
74
 
@@ -162,6 +162,9 @@ export class Dialog extends RapidElement {
162
162
  @property({ type: Boolean })
163
163
  destructive: boolean;
164
164
 
165
+ @property({ type: Boolean })
166
+ disabled: boolean;
167
+
165
168
  @property({ type: Boolean })
166
169
  loading: boolean;
167
170
 
@@ -293,6 +296,10 @@ export class Dialog extends RapidElement {
293
296
  );
294
297
  }
295
298
 
299
+ public getPrimaryButton(): Button {
300
+ return this.shadowRoot.querySelector(`temba-button[primary]`);
301
+ }
302
+
296
303
  private handleKeyUp(event: KeyboardEvent) {
297
304
  if (event.key === 'Escape') {
298
305
  this.clickCancel();
@@ -320,7 +327,9 @@ export class Dialog extends RapidElement {
320
327
  public render(): TemplateResult {
321
328
  const height = this.getDocumentHeight();
322
329
 
323
- const maskStyle = { height: `${height + 100}px` };
330
+ const maskStyle = {
331
+ height: `${height + 100}px`,
332
+ };
324
333
  const dialogStyle = { width: Dialog.widths[this.size] };
325
334
 
326
335
  const header = this.header
@@ -375,6 +384,7 @@ export class Dialog extends RapidElement {
375
384
  ?destructive=${this.destructive}
376
385
  ?primary=${!this.destructive}
377
386
  ?submitting=${this.submitting}
387
+ ?disabled=${this.disabled}
378
388
  >}</temba-button
379
389
  >
380
390
  `
@@ -106,9 +106,17 @@ export class Modax extends RapidElement {
106
106
  @property({ type: Boolean })
107
107
  noSubmit: boolean;
108
108
 
109
+ @property({ type: Object })
110
+ headers: any = {};
111
+
109
112
  @property({ type: String })
110
113
  body: any = this.getLoading();
111
114
 
115
+ @property({ type: Boolean })
116
+ disabled = false;
117
+
118
+ @property({ type: Boolean })
119
+ suspendSubmit = false;
112
120
  // private cancelToken: CancelTokenSource;
113
121
 
114
122
  // http promise to monitor for completeness
@@ -222,12 +230,18 @@ export class Modax extends RapidElement {
222
230
  return !scriptOnly;
223
231
  }
224
232
 
233
+ public getHeaders(): any {
234
+ const headers = this.headers;
235
+ headers['X-PJAX'] = 1;
236
+ return headers;
237
+ }
238
+
225
239
  private fetchForm() {
226
240
  // const CancelToken = axios.CancelToken;
227
241
  // this.cancelToken = CancelToken.source();
228
242
  this.fetching = true;
229
243
  this.body = this.getLoading();
230
- this.httpComplete = getUrl(this.endpoint, null, true).then(
244
+ this.httpComplete = getUrl(this.endpoint, null, this.getHeaders()).then(
231
245
  (response: WebResponse) => {
232
246
  this.setBody(response.body);
233
247
  this.updatePrimaryButton();
@@ -249,7 +263,7 @@ export class Modax extends RapidElement {
249
263
  this.httpComplete = postUrl(
250
264
  this.endpoint,
251
265
  postData,
252
- true,
266
+ this.getHeaders(),
253
267
  'application/x-www-form-urlencoded'
254
268
  )
255
269
  .then((response: WebResponse) => {
@@ -292,7 +306,9 @@ export class Modax extends RapidElement {
292
306
  const button = evt.detail.button;
293
307
  if (!button.disabled && !button.submitting) {
294
308
  if (button.name === this.primaryName) {
295
- this.submit();
309
+ if (!this.suspendSubmit) {
310
+ this.submit();
311
+ }
296
312
  }
297
313
  }
298
314
 
@@ -300,7 +316,6 @@ export class Modax extends RapidElement {
300
316
  this.open = false;
301
317
  this.fetching = false;
302
318
  this.cancelName = undefined;
303
- // this.cancelToken.cancel();
304
319
  }
305
320
  }
306
321
 
@@ -329,6 +344,7 @@ export class Modax extends RapidElement {
329
344
  ?submitting=${this.submitting}
330
345
  ?destructive=${this.isDestructive()}
331
346
  ?noFocus=${true}
347
+ ?disabled=${this.disabled}
332
348
  @temba-button-clicked=${this.handleDialogClick.bind(this)}
333
349
  @temba-dialog-hidden=${this.handleDialogHidden.bind(this)}
334
350
  >
package/src/interfaces.ts CHANGED
@@ -174,6 +174,7 @@ export enum CustomEventType {
174
174
  ScrollThreshold = 'temba-scroll-threshold',
175
175
  ContentChanged = 'temba-content-changed',
176
176
  ContextChanged = 'temba-context-changed',
177
+ FetchComplete = 'temba-fetch-complete',
177
178
  Submitted = 'temba-submitted',
178
179
  Redirected = 'temba-redirected',
179
180
  NoPath = 'temba-no-path',