@nyaruka/temba-components 0.20.0 → 0.24.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 (91) hide show
  1. package/.eslintrc.js +1 -0
  2. package/CHANGELOG.md +38 -0
  3. package/dist/ef1b57e2.js +356 -0
  4. package/dist/index.js +356 -1
  5. package/dist/static/icons/symbol-defs.svg +56 -20
  6. package/dist/sw.js +1 -1
  7. package/dist/sw.js.map +1 -1
  8. package/dist/templates/components-body.html +1 -1
  9. package/dist/templates/components-head.html +1 -1
  10. package/out-tsc/src/anchor/Anchor.js +25 -0
  11. package/out-tsc/src/anchor/Anchor.js.map +1 -0
  12. package/out-tsc/src/checkbox/Checkbox.js +29 -14
  13. package/out-tsc/src/checkbox/Checkbox.js.map +1 -1
  14. package/out-tsc/src/contacts/ContactDetails.js +9 -5
  15. package/out-tsc/src/contacts/ContactDetails.js.map +1 -1
  16. package/out-tsc/src/contacts/ContactHistory.js +1 -5
  17. package/out-tsc/src/contacts/ContactHistory.js.map +1 -1
  18. package/out-tsc/src/contacts/events.js +33 -7
  19. package/out-tsc/src/contacts/events.js.map +1 -1
  20. package/out-tsc/src/contactsearch/ContactSearch.js +126 -51
  21. package/out-tsc/src/contactsearch/ContactSearch.js.map +1 -1
  22. package/out-tsc/src/dialog/Dialog.js +11 -2
  23. package/out-tsc/src/dialog/Dialog.js.map +1 -1
  24. package/out-tsc/src/dialog/Modax.js +23 -4
  25. package/out-tsc/src/dialog/Modax.js.map +1 -1
  26. package/out-tsc/src/interfaces.js +1 -0
  27. package/out-tsc/src/interfaces.js.map +1 -1
  28. package/out-tsc/src/list/TembaMenu.js +384 -81
  29. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  30. package/out-tsc/src/omnibox/Omnibox.js +7 -1
  31. package/out-tsc/src/omnibox/Omnibox.js.map +1 -1
  32. package/out-tsc/src/select/Select.js +7 -1
  33. package/out-tsc/src/select/Select.js.map +1 -1
  34. package/out-tsc/src/textinput/TextInput.js +42 -1
  35. package/out-tsc/src/textinput/TextInput.js.map +1 -1
  36. package/out-tsc/src/utils/index.js +13 -14
  37. package/out-tsc/src/utils/index.js.map +1 -1
  38. package/out-tsc/src/vectoricon/VectorIcon.js +2 -1
  39. package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
  40. package/out-tsc/temba-modules.js +2 -0
  41. package/out-tsc/temba-modules.js.map +1 -1
  42. package/out-tsc/test/temba-menu.test.js +0 -13
  43. package/out-tsc/test/temba-menu.test.js.map +1 -1
  44. package/package.json +4 -4
  45. package/screenshots/truth/checkbox/checked.png +0 -0
  46. package/screenshots/truth/checkbox/default.png +0 -0
  47. package/screenshots/truth/contacts/history-expanded.png +0 -0
  48. package/screenshots/truth/list/items-selected.png +0 -0
  49. package/screenshots/truth/list/items-updated.png +0 -0
  50. package/screenshots/truth/list/items.png +0 -0
  51. package/screenshots/truth/list/menu-submenu.png +0 -0
  52. package/src/anchor/Anchor.ts +26 -0
  53. package/src/checkbox/Checkbox.ts +31 -16
  54. package/src/contacts/ContactDetails.ts +9 -5
  55. package/src/contacts/ContactHistory.ts +0 -4
  56. package/src/contacts/events.ts +33 -7
  57. package/src/contactsearch/ContactSearch.ts +132 -54
  58. package/src/dialog/Dialog.ts +12 -2
  59. package/src/dialog/Modax.ts +20 -4
  60. package/src/interfaces.ts +1 -0
  61. package/src/list/TembaMenu.ts +424 -93
  62. package/src/omnibox/Omnibox.ts +9 -1
  63. package/src/select/Select.ts +9 -1
  64. package/src/textinput/TextInput.ts +47 -1
  65. package/src/utils/index.ts +17 -16
  66. package/src/vectoricon/VectorIcon.ts +2 -1
  67. package/static/icons/Read Me.txt +1 -1
  68. package/static/icons/SVG/channel.svg +5 -0
  69. package/static/icons/SVG/cloud1.svg +5 -0
  70. package/static/icons/SVG/codepen.svg +5 -0
  71. package/static/icons/SVG/codesandbox.svg +5 -0
  72. package/static/icons/SVG/git-pull-request.svg +5 -0
  73. package/static/icons/SVG/grid.svg +5 -0
  74. package/static/icons/SVG/hard-drive.svg +5 -0
  75. package/static/icons/SVG/layout.svg +5 -0
  76. package/static/icons/SVG/list.svg +5 -0
  77. package/static/icons/SVG/map-pin.svg +5 -0
  78. package/static/icons/SVG/menu.svg +5 -0
  79. package/static/icons/SVG/package.svg +5 -0
  80. package/static/icons/SVG/zapier.svg +5 -0
  81. package/static/icons/demo-external-svg.html +232 -172
  82. package/static/icons/demo-files/demo.css +4 -4
  83. package/static/icons/demo.html +288 -192
  84. package/static/icons/selection.json +646 -345
  85. package/static/icons/style.css +4 -4
  86. package/static/icons/symbol-defs.svg +56 -20
  87. package/temba-modules.ts +2 -0
  88. package/test/temba-menu.test.ts +0 -16
  89. package/test-assets/style.css +1 -1
  90. package/dist/b10b5805.js +0 -1
  91. package/static/icons/SVG/zendesk1.svg +0 -5
@@ -9,6 +9,19 @@ export class Checkbox extends FormElement {
9
9
  display: inline-block;
10
10
  }
11
11
 
12
+ :host([label]) {
13
+ width: 100%;
14
+ }
15
+
16
+ .wrapper.label {
17
+ padding: 10px;
18
+ border-radius: var(--curvature);
19
+ }
20
+
21
+ .wrapper.label:hover {
22
+ background: #f9f9f9;
23
+ }
24
+
12
25
  temba-field {
13
26
  --help-text-margin-left: 24px;
14
27
  cursor: pointer;
@@ -93,22 +106,24 @@ export class Checkbox extends FormElement {
93
106
  />`;
94
107
 
95
108
  return html`
96
- <temba-field
97
- name=${this.name}
98
- .helpText=${this.helpText}
99
- .errors=${this.errors}
100
- .widgetOnly=${this.widgetOnly}
101
- .helpAlways=${true}
102
- ?disabled=${this.disabled}
103
- @click=${this.handleClick}
104
- >
105
- <div class="checkbox-container ${this.disabled ? 'disabled' : ''}">
106
- ${icon}
107
- ${this.label
108
- ? html`<div class="checkbox-label">${this.label}</div>`
109
- : null}
110
- </div>
111
- </temba-field>
109
+ <div class="wrapper ${this.label ? 'label' : ''}">
110
+ <temba-field
111
+ name=${this.name}
112
+ .helpText=${this.helpText}
113
+ .errors=${this.errors}
114
+ .widgetOnly=${this.widgetOnly}
115
+ .helpAlways=${true}
116
+ ?disabled=${this.disabled}
117
+ @click=${this.handleClick}
118
+ >
119
+ <div class="checkbox-container ${this.disabled ? 'disabled' : ''}">
120
+ ${icon}
121
+ ${this.label
122
+ ? html`<div class="checkbox-label">${this.label}</div>`
123
+ : null}
124
+ </div>
125
+ </temba-field>
126
+ </div>
112
127
  `;
113
128
  }
114
129
  }
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/camelcase */
2
1
  import { css, html, property, TemplateResult } from 'lit-element';
3
2
  import { Contact, Group, Ticket } from '../interfaces';
4
3
  import { RapidElement } from '../RapidElement';
@@ -265,13 +264,18 @@ export class ContactDetails extends RapidElement {
265
264
  ${this.showGroups && !this.ticket
266
265
  ? html`<div class="groups">
267
266
  ${this.contact.groups.map((group: Group) => {
268
- return html`<a href="/contacts/filter/${group.uuid}/"
269
- ><div class="group-label" style="cursor:pointer">
267
+ return html`
268
+ <div
269
+ onclick="goto(event)"
270
+ href="/contact/filter/${group.uuid}/"
271
+ class="group-label"
272
+ style="cursor:pointer"
273
+ >
270
274
  ${group.is_dynamic
271
275
  ? html`<temba-icon name="atom"></temba-icon>`
272
276
  : null}${group.name}
273
- </div></a
274
- >`;
277
+ </div>
278
+ `;
275
279
  })}
276
280
  </div>`
277
281
  : html``}
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/camelcase */
2
1
  import { css, property } from 'lit-element';
3
2
  import { html, TemplateResult } from 'lit-html';
4
3
  import { Contact, CustomEventType, Ticket } from '../interfaces';
@@ -898,7 +897,6 @@ export class ContactHistory extends RapidElement {
898
897
  body: ticket.body,
899
898
  ticketer: ticket.ticketer,
900
899
  },
901
- // eslint-disable-next-line @typescript-eslint/camelcase
902
900
  created_on: ticket.opened_on,
903
901
  };
904
902
 
@@ -918,8 +916,6 @@ export class ContactHistory extends RapidElement {
918
916
  return html`
919
917
  ${this.ticket
920
918
  ? html`<div class="sticky-bin">${unfetchedTickets}</div>`
921
- : this.contact
922
- ? html`<div class="scroll-title">${this.contact.name}</div>`
923
919
  : null}
924
920
  ${this.fetching
925
921
  ? html`<temba-loading units="5" size="10"></temba-loading>`
@@ -686,7 +686,7 @@ export const renderAttachment = (attachment: string): TemplateResult => {
686
686
 
687
687
  let inner = null;
688
688
  if (mediaType === 'image') {
689
- inner = html`<a href="${url}"><img src="${url}" style="width:100%;height:auto;display:block"></img></a>`;
689
+ inner = html`<div class="linked" onclick="goto(event)" href="${url}"><img src="${url}" style="width:100%;height:auto;display:block"></img></a>`;
690
690
  } else if (ext === 'pdf') {
691
691
  return html`<div
692
692
  style="width:100%;height:300px;border-radius:var(--curvature);box-shadow:0px 0px 10px -1px rgb(160 160 160);overflow:hidden"
@@ -928,7 +928,13 @@ export const renderTicketAction = (
928
928
  <temba-icon name="${icon}"></temba-icon>
929
929
  <div class="description">
930
930
  ${getDisplayName(event.created_by)} ${action} a
931
- <a href="/tickets/all/open/${event.ticket.uuid}">ticket</a>
931
+ <span
932
+ onclick="goto(event)"
933
+ class="linked"
934
+ href="/ticket/all/open/${event.ticket.uuid}"
935
+ >
936
+ ticket
937
+ </span>
932
938
  </div>
933
939
  </div>`;
934
940
  }
@@ -976,7 +982,13 @@ export const renderTicketOpened = (
976
982
  <temba-icon name="${icon}"></temba-icon>
977
983
  <div class="description">
978
984
  ${event.ticket.topic.name}
979
- <a href="/tickets/all/open/${event.ticket.uuid}">ticket</a> was opened
985
+ <span
986
+ class="linked"
987
+ onclick="goto(event)"
988
+ href="/tickets/all/open/${event.ticket.uuid}"
989
+ >ticket</span
990
+ >
991
+ was opened
980
992
  </div>
981
993
  </div>`;
982
994
  } else {
@@ -1114,11 +1126,20 @@ export const renderCampaignFiredEvent = (
1114
1126
  return html`<temba-icon name="campaign"></temba-icon>
1115
1127
  <div class="description">
1116
1128
  Campaign
1117
- <a href="/campaign/read/${event.campaign.id}">${event.campaign.name}</a>
1129
+ <span
1130
+ class="linked"
1131
+ onclick="goto(event)"
1132
+ href="/campaign/read/${event.campaign.id}"
1133
+ >${event.campaign.name}</span
1134
+ >
1118
1135
  ${event.fired_result === 'S' ? 'skipped' : 'triggered'}
1119
- <a href="/campaignevent/read/${event.campaign_event.id}">
1136
+ <span
1137
+ class="linked"
1138
+ onclick="goto(event)"
1139
+ href="/campaignevent/read/${event.campaign_event.id}"
1140
+ >
1120
1141
  ${event.campaign_event.offset_display}
1121
- ${event.campaign_event.relative_to.name}</a
1142
+ ${event.campaign_event.relative_to.name}</span
1122
1143
  >
1123
1144
  </div>`;
1124
1145
  };
@@ -1138,7 +1159,12 @@ export const renderContactGroupsEvent = (
1138
1159
  ${oxfordFn(
1139
1160
  groups,
1140
1161
  (group: ObjectReference) =>
1141
- html`<a href="/contact/filter/${group.uuid}">${group.name}</a>`
1162
+ html`<span
1163
+ class="linked"
1164
+ onclick="goto(event)"
1165
+ href="/contact/filter/${group.uuid}"
1166
+ >${group.name}</span
1167
+ >`
1142
1168
  )}
1143
1169
  ${event.type === Events.FAILURE
1144
1170
  ? html`<div>Run ended prematurely, check the flow design.</div>`
@@ -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
 
@@ -53,14 +53,11 @@ export class ContactSearch extends FormElement {
53
53
  vertical-align: top;
54
54
  }
55
55
 
56
- table {
57
- width: 100%;
58
- padding-top: 10px;
56
+ .summary {
59
57
  }
60
58
 
61
- .header td {
62
- border-bottom: 2px solid var(--color-borders);
63
- padding: 5px 3px;
59
+ table {
60
+ width: 100%;
64
61
  }
65
62
 
66
63
  .contact td {
@@ -92,6 +89,46 @@ export class ContactSearch extends FormElement {
92
89
  .error {
93
90
  margin-top: 10px;
94
91
  }
92
+
93
+ .match-count {
94
+ padding: 4px;
95
+ margin-top: 6px;
96
+ }
97
+
98
+ .linked {
99
+ color: var(--color-link-primary);
100
+ text-decoration: none;
101
+ cursor: pointer;
102
+ }
103
+
104
+ .header td {
105
+ border-bottom: 0px solid var(--color-borders);
106
+ padding: 5px 3px;
107
+ }
108
+
109
+ .expanded .header td {
110
+ border-bottom: 2px solid var(--color-borders);
111
+ }
112
+
113
+ td.field-header,
114
+ tr.table-footer,
115
+ tr.contact {
116
+ display: none;
117
+ }
118
+
119
+ .expanded td.field-header {
120
+ display: table-cell;
121
+ }
122
+
123
+ .expanded tr.contact,
124
+ .expanded tr.table-footer {
125
+ display: table-row;
126
+ }
127
+
128
+ .query {
129
+ display: var(--contact-search-query-display);
130
+ margin-bottom: 10px;
131
+ }
95
132
  `;
96
133
  }
97
134
 
@@ -100,6 +137,9 @@ export class ContactSearch extends FormElement {
100
137
  @property({ type: Boolean })
101
138
  fetching: boolean;
102
139
 
140
+ @property({ type: Boolean })
141
+ expanded: boolean;
142
+
103
143
  @property({ type: String })
104
144
  endpoint: string;
105
145
 
@@ -112,8 +152,11 @@ export class ContactSearch extends FormElement {
112
152
  @property({ type: String })
113
153
  query = '';
114
154
 
115
- @property({ type: String, attribute: 'matches-text' })
116
- matchesText = '';
155
+ @property({ type: Number })
156
+ inactiveThreshold = 1000;
157
+
158
+ @property({ type: Number })
159
+ inactiveDays = 90;
117
160
 
118
161
  @property({ attribute: false })
119
162
  summary: SummaryResponse;
@@ -140,16 +183,22 @@ export class ContactSearch extends FormElement {
140
183
  }
141
184
  }
142
185
 
143
- public fetchSummary(query: string): any {
144
- // const CancelToken = axios.CancelToken;
145
- // this.cancelToken = CancelToken.source();
146
-
147
- const url = this.endpoint + query;
186
+ public executeQuery(query: string): any {
187
+ const url = this.endpoint + query.replace('\n', ' ');
188
+ getUrl(url).then((response: WebResponse) => {
189
+ if (response.status === 200) {
190
+ const summary = response.json as SummaryResponse;
191
+ this.fireCustomEvent(CustomEventType.FetchComplete, summary);
192
+ }
193
+ });
194
+ }
148
195
 
196
+ public fetchSummary(query: string): any {
197
+ const url = this.endpoint + query.replace('\n', ' ');
149
198
  getUrl(url).then((response: WebResponse) => {
199
+ this.fetching = false;
150
200
  if (response.status === 200) {
151
201
  this.summary = response.json as SummaryResponse;
152
- this.fetching = false;
153
202
 
154
203
  if (this.summary.error) {
155
204
  this.errors = [this.summary.error];
@@ -157,6 +206,7 @@ export class ContactSearch extends FormElement {
157
206
  this.errors = [];
158
207
  }
159
208
  this.requestUpdate('errors');
209
+ this.fireCustomEvent(CustomEventType.ContentChanged, this.summary);
160
210
  }
161
211
  });
162
212
  }
@@ -177,20 +227,29 @@ export class ContactSearch extends FormElement {
177
227
 
178
228
  if (!this.summary.error) {
179
229
  const count = this.summary.total;
180
- const message = fillTemplate(this.matchesText, {
181
- query: this.summary.query,
182
- count,
183
- });
230
+ const lastSeenOn = this.summary.query.indexOf('last_seen_on') > -1;
184
231
 
185
232
  summary = html`
233
+ <div class="summary ${this.expanded ? 'expanded' : ''}">
186
234
  <table cellspacing="0" cellpadding="0">
187
235
  <tr class="header">
188
- <td colspan="2"></td>
236
+ <td colspan="2">
237
+ Found <a
238
+ class="linked"
239
+ target="_"
240
+ href="/contact/?search=${encodeURIComponent(this.summary.query)}"
241
+
242
+ >${count.toLocaleString()}</a> contact${
243
+ count !== 1 ? 's' : ''
244
+ }</div>
245
+ </td>
189
246
  ${fields.map(
190
247
  field => html` <td class="field-header">${field.label}</td> `
191
248
  )}
192
249
  <td></td>
193
- <td class="field-header created-on">Created On</td>
250
+ <td class="field-header date">
251
+ ${lastSeenOn ? 'Last Seen' : 'Created'}
252
+ </td>
194
253
  </tr>
195
254
 
196
255
  ${this.summary.sample.map(
@@ -207,24 +266,35 @@ export class ContactSearch extends FormElement {
207
266
  `
208
267
  )}
209
268
  <td></td>
210
- <td class="created-on">${contact.created_on}</td>
269
+ <td class="date">
270
+ ${lastSeenOn
271
+ ? contact.last_seen_on || '--'
272
+ : contact.created_on}
273
+ </td>
211
274
  </tr>
212
275
  `
213
276
  )}
214
277
 
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>
278
+ ${
279
+ this.summary.total > this.summary.sample.length
280
+ ? html`<tr class="table-footer">
281
+ <td class="query-details" colspan=${fields.length + 3}></td>
282
+ <td class="more">
283
+ <a
284
+ class="linked"
285
+ target="_"
286
+ href="/contact/?search=${encodeURIComponent(
287
+ this.summary.query
288
+ )}"
289
+ >more</a
290
+ >
291
+ </td>
292
+ </tr>`
293
+ : null
294
+ }
295
+
227
296
  </table>
297
+ </div>
228
298
  `;
229
299
  }
230
300
  }
@@ -232,23 +302,31 @@ export class ContactSearch extends FormElement {
232
302
  const loadingStyle = this.fetching ? { opacity: '1' } : {};
233
303
 
234
304
  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}
305
+ <div class="query">
306
+ <temba-textinput
307
+ .label=${this.label}
308
+ .helpText=${this.helpText}
309
+ .widgetOnly=${this.widgetOnly}
310
+ .errors=${this.errors}
311
+ name=${this.name}
312
+ .inputRoot=${this}
313
+ @input=${this.handleQueryChange}
314
+ placeholder=${this.placeholder}
315
+ .value=${this.query}
316
+ textarea
317
+ autogrow
318
+ >
319
+ </temba-textinput>
320
+ </div>
321
+
322
+ ${this.fetching
323
+ ? html`<temba-loading
324
+ units="4"
325
+ style=${styleMap(loadingStyle)}
326
+ ></temba-loading>`
327
+ : this.summary
328
+ ? html` <div class="summary">${summary}</div> `
329
+ : null}
252
330
  `;
253
331
  }
254
332
  }
@@ -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',