@nyaruka/temba-components 0.55.0 → 0.56.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 (56) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/{31b8df84.js → 86c3ca3f.js} +186 -103
  3. package/dist/index.js +186 -103
  4. package/dist/locales/es.js +6 -0
  5. package/dist/locales/es.js.map +1 -1
  6. package/dist/locales/fr.js +6 -0
  7. package/dist/locales/fr.js.map +1 -1
  8. package/dist/locales/pt.js +6 -0
  9. package/dist/locales/pt.js.map +1 -1
  10. package/dist/static/svg/index.svg +1 -1
  11. package/dist/sw.js +1 -1
  12. package/dist/sw.js.map +1 -1
  13. package/dist/templates/components-body.html +1 -1
  14. package/dist/templates/components-head.html +1 -1
  15. package/out-tsc/src/FormElement.js +6 -0
  16. package/out-tsc/src/FormElement.js.map +1 -1
  17. package/out-tsc/src/button/Button.js +1 -1
  18. package/out-tsc/src/button/Button.js.map +1 -1
  19. package/out-tsc/src/contactsearch/ContactSearch.js +244 -95
  20. package/out-tsc/src/contactsearch/ContactSearch.js.map +1 -1
  21. package/out-tsc/src/label/Label.js +2 -3
  22. package/out-tsc/src/label/Label.js.map +1 -1
  23. package/out-tsc/src/locales/es.js +6 -0
  24. package/out-tsc/src/locales/es.js.map +1 -1
  25. package/out-tsc/src/locales/fr.js +6 -0
  26. package/out-tsc/src/locales/fr.js.map +1 -1
  27. package/out-tsc/src/locales/pt.js +6 -0
  28. package/out-tsc/src/locales/pt.js.map +1 -1
  29. package/out-tsc/src/omnibox/Omnibox.js +10 -3
  30. package/out-tsc/src/omnibox/Omnibox.js.map +1 -1
  31. package/out-tsc/src/select/Select.js +21 -11
  32. package/out-tsc/src/select/Select.js.map +1 -1
  33. package/out-tsc/src/vectoricon/VectorIcon.js +20 -0
  34. package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
  35. package/out-tsc/src/vectoricon/index.js +3 -1
  36. package/out-tsc/src/vectoricon/index.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/FormElement.ts +8 -0
  39. package/src/button/Button.ts +1 -1
  40. package/src/contactsearch/ContactSearch.ts +270 -113
  41. package/src/label/Label.ts +2 -3
  42. package/src/locales/es.ts +6 -0
  43. package/src/locales/fr.ts +6 -0
  44. package/src/locales/pt.ts +6 -0
  45. package/src/omnibox/Omnibox.ts +10 -4
  46. package/src/select/Select.ts +21 -11
  47. package/src/vectoricon/VectorIcon.ts +20 -0
  48. package/src/vectoricon/index.ts +4 -1
  49. package/static/svg/index.svg +1 -1
  50. package/static/svg/work/traced/edit-03.svg +1 -0
  51. package/static/svg/work/traced/flip-backward.svg +1 -0
  52. package/static/svg/work/used/edit-03.svg +3 -0
  53. package/static/svg/work/used/flip-backward.svg +3 -0
  54. package/xliff/es.xlf +18 -0
  55. package/xliff/fr.xlf +18 -0
  56. package/xliff/pt.xlf +18 -0
@@ -1,13 +1,16 @@
1
- import { TemplateResult, html, css } from 'lit';
1
+ import { TemplateResult, html, css, PropertyValueMap } from 'lit';
2
+ import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
2
3
  import { property } from 'lit/decorators.js';
3
- import { getClasses, postJSON, WebResponse } from '../utils';
4
+ import { getClasses, postJSON, stopEvent, WebResponse } from '../utils';
4
5
  import { TextInput } from '../textinput/TextInput';
5
6
  import '../alert/Alert';
6
7
  import { Contact, CustomEventType } from '../interfaces';
7
8
  import { FormElement } from '../FormElement';
8
9
  import { Checkbox } from '../checkbox/Checkbox';
10
+ import { msg } from '@lit/localize';
11
+ import { OmniOption, Omnibox } from '../omnibox/Omnibox';
9
12
 
10
- const QUEIT_MILLIS = 1000;
13
+ const QUEIT_MILLIS = 2000;
11
14
 
12
15
  interface SummaryResponse {
13
16
  total: number;
@@ -15,6 +18,8 @@ interface SummaryResponse {
15
18
  query: string;
16
19
  fields: { [uuid: string]: { label: string; type: string } };
17
20
  error?: string;
21
+ warnings: string[];
22
+ blockers: string[];
18
23
  }
19
24
 
20
25
  export class ContactSearch extends FormElement {
@@ -142,6 +147,17 @@ export class ContactSearch extends FormElement {
142
147
 
143
148
  .summary {
144
149
  min-height: 2.2em;
150
+ display: flex;
151
+ flex-grow: 1;
152
+ align-items: center;
153
+ }
154
+
155
+ .summary .result-count {
156
+ flex-grow: 1;
157
+ }
158
+
159
+ .results.empty {
160
+ display: none !important;
145
161
  }
146
162
 
147
163
  .results.initialized {
@@ -150,10 +166,44 @@ export class ContactSearch extends FormElement {
150
166
  margin-top: 0.5em;
151
167
  margin-left: 0.6em;
152
168
  }
169
+
170
+ .advanced-icon {
171
+ cursor: pointer;
172
+ margin-right: 0.5em;
173
+ }
174
+
175
+ .query .advanced-icon {
176
+ margin-top: 1em;
177
+ margin-right: 1em;
178
+ }
179
+
180
+ .advanced-icon:hover {
181
+ --icon-color: var(--color-link-primary-hover) !important;
182
+ }
183
+
184
+ .query {
185
+ --textarea-height: 5em;
186
+ }
187
+
188
+ #recipients {
189
+ margin-bottom: 1em;
190
+ display: block;
191
+ }
192
+
193
+ temba-alert {
194
+ margin: 1em 0;
195
+ }
153
196
  `;
154
197
  }
155
198
 
156
- // private cancelToken: CancelTokenSource;
199
+ @property({ type: Boolean })
200
+ in_a_flow: boolean;
201
+
202
+ @property({ type: Boolean })
203
+ started_previously: boolean;
204
+
205
+ @property({ type: Boolean })
206
+ not_seen_since_days: boolean;
157
207
 
158
208
  @property({ type: Boolean })
159
209
  fetching: boolean;
@@ -185,44 +235,90 @@ export class ContactSearch extends FormElement {
185
235
  @property({ type: Object, attribute: false })
186
236
  flow: any;
187
237
 
188
- private lastQuery: number;
189
- private initialized = false;
238
+ @property({ type: Array })
239
+ recipients: OmniOption[] = [];
190
240
 
241
+ @property({ type: Boolean })
242
+ advanced = false;
243
+
244
+ @property({ type: String })
245
+ refreshKey = '0';
246
+
247
+ public refresh(): void {
248
+ this.refreshKey = 'requested_' + new Date().getTime();
249
+ }
250
+
251
+ @property({ type: Object })
191
252
  private exclusions = {};
192
253
 
254
+ private lastQuery: number;
255
+ private initialized = false;
256
+
193
257
  public updated(changedProperties: Map<string, any>) {
194
258
  super.updated(changedProperties);
195
259
 
196
- if (changedProperties.has('query') || changedProperties.has('endpoint')) {
197
- this.fetching = !!this.query && !!this.endpoint;
260
+ if (changedProperties.has('advanced') && this.advanced) {
261
+ return;
262
+ }
198
263
 
199
- if (this.fetching) {
200
- this.initialized = true;
201
- // clear our summary on any change
202
- this.summary = null;
203
- if (this.lastQuery) {
204
- window.clearTimeout(this.lastQuery);
205
- }
264
+ // if we remove the in_a_flow option, make sure it's not part of our exclusions
265
+ if (changedProperties.has('in_a_flow') && !this.in_a_flow) {
266
+ delete this.exclusions['in_a_flow'];
267
+ this.requestUpdate('exclusions');
268
+ }
206
269
 
207
- if (this.query.trim().length > 0) {
208
- this.lastQuery = window.setTimeout(() => {
209
- this.fetchSummary(this.query);
210
- }, QUEIT_MILLIS);
211
- }
270
+ if (
271
+ (changedProperties.has('query') && this.advanced) ||
272
+ (changedProperties.has('refreshKey') && this.refreshKey !== '0')
273
+ ) {
274
+ this.summary = null;
275
+ // this.errors = [];
276
+
277
+ this.fireCustomEvent(CustomEventType.ContentChanged, { reset: true });
278
+ if (this.lastQuery) {
279
+ window.clearTimeout(this.lastQuery);
280
+ this.fetching = false;
281
+ }
282
+
283
+ if (this.query.trim().length > 0 || this.recipients.length > 0) {
284
+ this.fetching = true;
285
+ this.lastQuery = window.setTimeout(() => {
286
+ this.fetchSummary();
287
+ }, QUEIT_MILLIS);
212
288
  }
213
289
  }
214
290
  }
215
291
 
216
- public fetchSummary(query: string): any {
292
+ public fetchSummary(): any {
217
293
  if (this.endpoint) {
294
+ const group_uuids = this.recipients
295
+ .filter((value: OmniOption) => value.type === 'group')
296
+ .map((value: OmniOption) => value.id);
297
+
298
+ const contact_uuids = this.recipients
299
+ .filter((value: OmniOption) => value.type === 'contact')
300
+ .map((value: OmniOption) => value.id);
301
+
218
302
  postJSON(this.endpoint, {
219
- include: { query },
303
+ include: this.advanced
304
+ ? { query: this.query }
305
+ : { contact_uuids, group_uuids },
306
+
220
307
  exclude: this.exclusions,
221
308
  }).then((response: WebResponse) => {
222
309
  this.fetching = false;
223
310
  if (response.status === 200) {
224
311
  this.summary = response.json as SummaryResponse;
225
- this.value = this.summary.query;
312
+ if (!this.advanced) {
313
+ this.query = this.summary.query;
314
+ }
315
+ this.setValue({
316
+ advanced: this.advanced,
317
+ query: this.query,
318
+ exclusions: this.exclusions,
319
+ recipients: this.recipients,
320
+ });
321
+
226
322
  if (this.summary.error) {
227
323
  this.errors = [this.summary.error];
228
324
  } else {
@@ -242,13 +338,40 @@ export class ContactSearch extends FormElement {
242
338
  }
243
339
  }
244
340
 
341
+ private handleAdvancedToggle(evt: MouseEvent) {
342
+ stopEvent(evt);
343
+ this.recipients = [];
344
+ this.exclusions = {};
345
+ if (this.advanced) {
346
+ this.query = '';
347
+ this.value = null;
348
+ }
349
+ this.advanced = !this.advanced;
350
+
351
+ this.setValue({
352
+ advanced: this.advanced,
353
+ query: this.query,
354
+ exclusions: this.exclusions,
355
+ recipients: this.recipients,
356
+ });
357
+ }
358
+
245
359
  private handleQueryChange(evt: KeyboardEvent) {
246
360
  const input = evt.target as TextInput;
247
361
  this.query = input.inputElement.value;
248
362
  }
249
363
 
250
- private handleSlotChanged(evt: any) {
364
+ private handleRecipientsChanged(evt: any) {
365
+ if (this.refreshKey !== '0' || this.initialized) {
366
+ this.refresh();
367
+ } else {
368
+ this.initialized = true;
369
+ }
370
+ }
371
+
372
+ private handleExclusionChanged(evt: any) {
251
373
  if (evt.target.tagName === 'TEMBA-CHECKBOX') {
374
+ const ex = JSON.stringify(this.exclusions);
252
375
  const checkbox = evt.target as Checkbox;
253
376
  let value = checkbox.checked as any;
254
377
 
@@ -261,120 +384,154 @@ export class ContactSearch extends FormElement {
261
384
 
262
385
  this.exclusions[checkbox.name] = value;
263
386
  }
264
- }
265
387
 
266
- this.requestUpdate('query');
388
+ if (ex !== JSON.stringify(this.exclusions)) {
389
+ this.refresh();
390
+ }
391
+ }
267
392
  }
268
393
 
269
394
  public render(): TemplateResult {
270
395
  let summary: TemplateResult;
271
396
  if (this.summary) {
272
- const fields = Object.keys(this.summary.fields || []).map(
273
- (uuid: string) => {
274
- return { uuid, ...this.summary.fields[uuid] };
275
- }
276
- );
277
-
278
397
  if (!this.summary.error) {
279
398
  const count = this.summary.total;
280
- const lastSeenOn = this.summary.query.indexOf('last_seen_on') > -1;
281
399
 
282
400
  summary = html`
283
- <table cellspacing="0" cellpadding="0">
284
- <tr class="header">
285
- <td colspan="2">
286
- Found
287
- <a
288
- class="linked"
289
- target="_"
290
- href="/contact/?search=${encodeURIComponent(
291
- this.summary.query
292
- )}"
293
- >
294
- ${count.toLocaleString()}
295
- </a>
296
- contact${count !== 1 ? 's' : ''}
297
- </td>
298
- ${fields.map(
299
- field => html` <td class="field-header">${field.label}</td> `
300
- )}
301
- <td></td>
302
- <td class="field-header date">
303
- ${lastSeenOn ? 'Last Seen' : 'Created'}
304
- </td>
305
- </tr>
306
-
307
- ${this.summary.sample.map(
308
- (contact: Contact) => html`
309
- <tr class="contact">
310
- <td class="urn">${(contact as any).primary_urn_formatted}</td>
311
- <td class="name">${contact.name}</td>
312
- ${fields.map(
313
- field => html`
314
- <td class="field">
315
- ${((contact as any).fields[field.uuid] || { text: '' })
316
- .text}
317
- </td>
318
- `
319
- )}
320
- <td></td>
321
- <td class="date">
322
- ${lastSeenOn
323
- ? contact.last_seen_on || '--'
324
- : contact.created_on}
325
- </td>
326
- </tr>
327
- `
328
- )}
329
- ${this.summary.total > this.summary.sample.length
330
- ? html`<tr class="table-footer">
331
- <td class="query-details" colspan=${fields.length + 3}></td>
332
- <td class="more">
333
- <a
334
- class="linked"
335
- target="_"
336
- href="/contact/?search=${encodeURIComponent(
337
- this.summary.query
338
- )}"
339
- >more</a
340
- >
341
- </td>
342
- </tr>`
343
- : null}
344
- </table>
401
+ <div class="result-count">
402
+ Found
403
+ <a
404
+ class="linked"
405
+ target="_"
406
+ href="/contact/?search=${encodeURIComponent(this.summary.query)}"
407
+ >
408
+ ${count.toLocaleString()}
409
+ </a>
410
+ contact${count !== 1 ? 's' : ''}
411
+ </div>
412
+ <temba-button
413
+ class="edit"
414
+ name="edit"
415
+ secondary
416
+ small
417
+ @click=${this.handleAdvancedToggle}
418
+ >
419
+ <div slot="name">
420
+ <div style="display: flex; align-items: center;">
421
+ ${this.advanced
422
+ ? html` <temba-icon
423
+ name="reset"
424
+ style="margin-right:0.5em"
425
+ ></temba-icon>
426
+ Start Over`
427
+ : html` <temba-icon
428
+ name="edit"
429
+ style="margin-right:0.5em"
430
+ ></temba-icon>
431
+ Edit Query`}
432
+ </div>
433
+ </div>
434
+ </temba-button>
345
435
  `;
346
436
  }
347
437
  }
348
438
 
349
- return html`
350
- <div class="query">
351
- <temba-textinput
352
- .label=${this.label}
353
- .helpText=${this.helpText}
354
- .widgetOnly=${this.widgetOnly}
355
- .errors=${this.errors}
356
- name=${this.name}
357
- .inputRoot=${this}
358
- @input=${this.handleQueryChange}
359
- placeholder=${this.placeholder}
360
- .value=${this.query}
361
- textarea
362
- autogrow
363
- >
364
- </temba-textinput>
365
- </div>
439
+ if (this.summary && this.summary.blockers.length > 0) {
440
+ return html`${this.summary.blockers.map(
441
+ error =>
442
+ html`<temba-alert level="error">${unsafeHTML(error)}</temba-alert>`
443
+ )}`;
444
+ }
366
445
 
367
- <slot @change=${this.handleSlotChanged}></slot>
446
+ return html`
447
+ ${this.advanced
448
+ ? html`<div class="query">
449
+ <temba-textinput
450
+ .label=${this.label}
451
+ .helpText=${this.helpText}
452
+ .widgetOnly=${this.widgetOnly}
453
+ .errors=${this.errors}
454
+ name=${this.name}
455
+ .inputRoot=${this}
456
+ @input=${this.handleQueryChange}
457
+ placeholder=${this.placeholder}
458
+ .value=${this.query}
459
+ textarea
460
+ autogrow
461
+ >
462
+ </temba-textinput>
463
+ </div>`
464
+ : html`<temba-omnibox
465
+ placeholder="Search for contacts or groups"
466
+ widget_only=""
467
+ groups=""
468
+ contacts=""
469
+ label="Recipients"
470
+ help_text="The contacts to send the message to."
471
+ .errors=${this.errors}
472
+ id="recipients"
473
+ name="recipients"
474
+ .value=${this.recipients}
475
+ endpoint="/contact/omnibox/?"
476
+ @change=${this.handleRecipientsChanged}
477
+ >
478
+ </temba-omnibox>
479
+
480
+ ${this.not_seen_since_days
481
+ ? html`<temba-checkbox
482
+ name="not_seen_since_days"
483
+ label="${msg('Skip inactive contacts')}"
484
+ help_text="${msg(
485
+ 'Only include contacts who have sent a message in the last 90 days.'
486
+ )}"
487
+ ?checked=${this.exclusions['not_seen_since_days'] === 90}
488
+ @change=${this.handleExclusionChanged}
489
+ ></temba-checkbox>`
490
+ : null}
491
+ ${this.in_a_flow
492
+ ? html`<temba-checkbox
493
+ name="in_a_flow"
494
+ label="${msg('Skip contacts currently in a flow')}"
495
+ help_text="${msg(
496
+ 'Avoid interrupting a contact who is already in a flow.'
497
+ )}"
498
+ ?checked=${this.exclusions['in_a_flow']}
499
+ @change=${this.handleExclusionChanged}
500
+ ></temba-checkbox>`
501
+ : null}
502
+ ${this.started_previously
503
+ ? html`<temba-checkbox
504
+ name="started_previously"
505
+ label="${msg('Skip repeat contacts')}"
506
+ help_text="${msg(
507
+ 'Avoid restarting a contact who has been in this flow in the last 90 days.'
508
+ )}"
509
+ ?checked=${this.exclusions['started_previously']}
510
+ @change=${this.handleExclusionChanged}
511
+ ></temba-checkbox>`
512
+ : null}`}
368
513
 
369
514
  <div
370
515
  class="results ${getClasses({
371
516
  fetching: this.fetching,
372
517
  initialized: this.initialized || this.fetching,
518
+ empty:
519
+ ((this.summary && this.summary.error) || !this.summary) &&
520
+ !this.fetching,
373
521
  })}"
374
522
  >
375
523
  <temba-loading units="6" size="8"></temba-loading>
376
524
  <div class="summary ${this.expanded ? 'expanded' : ''}">${summary}</div>
377
525
  </div>
526
+
527
+ ${this.summary && this.summary.warnings
528
+ ? this.summary.warnings.map(
529
+ warning =>
530
+ html`<temba-alert level="warning"
531
+ >${unsafeHTML(warning)}</temba-alert
532
+ >`
533
+ )
534
+ : ``}
378
535
  `;
379
536
  }
380
537
  }
@@ -33,7 +33,7 @@ export default class Label extends LitElement {
33
33
  font-size: 0.8em;
34
34
  font-weight: 400;
35
35
  border-radius: 12px;
36
- box-shadow: 0 0.04em 0.08em rgba(0, 0, 0, 0.15);
36
+ box-shadow: var(--widget-shadow, 0 0.04em 0.08em rgba(0, 0, 0, 0.15));
37
37
  background: var(--color-overlay-light);
38
38
  color: var(--color-overlay-light-text);
39
39
  --icon-color: var(--color-overlay-light-text);
@@ -66,8 +66,6 @@ export default class Label extends LitElement {
66
66
 
67
67
  .dark {
68
68
  background: var(--color-overlay-dark);
69
- color: var(--color-overlay-dark-text);
70
- --icon-color: var(--color-overlay-dark-text);
71
69
  text-shadow: none;
72
70
  }
73
71
 
@@ -132,6 +130,7 @@ export default class Label extends LitElement {
132
130
  tertiary: this.tertiary,
133
131
  shadow: this.shadow,
134
132
  danger: this.danger,
133
+ dark: this.dark,
135
134
  })}"
136
135
  style=${styleMap(labelStyle)}
137
136
  >
package/src/locales/es.ts CHANGED
@@ -6,4 +6,10 @@
6
6
 
7
7
  export const templates = {
8
8
  scf1453991c986b25: `Tab para completar, enter para seleccionar`,
9
+ sf8653793d61d060c: `Skip inactive contacts`,
10
+ sd4af861b95e8ba4a: `Only include contacts who have sent a message in the last 90 days.`,
11
+ sd149dff460c8dc41: `Skip contacts currently in a flow`,
12
+ sc85010c81b71421e: `Avoid interrupting a contact who is already in a flow.`,
13
+ s3e3fa53e834f4fda: `Skip repeat contacts`,
14
+ s95e715d82602bced: `Avoid restarting a contact who has been in this flow in the last 90 days.`,
9
15
  };
package/src/locales/fr.ts CHANGED
@@ -6,4 +6,10 @@
6
6
 
7
7
  export const templates = {
8
8
  scf1453991c986b25: `Tab to complete, enter to select`,
9
+ sf8653793d61d060c: `Skip inactive contacts`,
10
+ sd4af861b95e8ba4a: `Only include contacts who have sent a message in the last 90 days.`,
11
+ sd149dff460c8dc41: `Skip contacts currently in a flow`,
12
+ sc85010c81b71421e: `Avoid interrupting a contact who is already in a flow.`,
13
+ s3e3fa53e834f4fda: `Skip repeat contacts`,
14
+ s95e715d82602bced: `Avoid restarting a contact who has been in this flow in the last 90 days.`,
9
15
  };
package/src/locales/pt.ts CHANGED
@@ -6,4 +6,10 @@
6
6
 
7
7
  export const templates = {
8
8
  scf1453991c986b25: `Tab to complete, enter to select`,
9
+ sf8653793d61d060c: `Skip inactive contacts`,
10
+ sd4af861b95e8ba4a: `Only include contacts who have sent a message in the last 90 days.`,
11
+ sd149dff460c8dc41: `Skip contacts currently in a flow`,
12
+ sc85010c81b71421e: `Avoid interrupting a contact who is already in a flow.`,
13
+ s3e3fa53e834f4fda: `Skip repeat contacts`,
14
+ s95e715d82602bced: `Avoid restarting a contact who has been in this flow in the last 90 days.`,
9
15
  };
@@ -10,7 +10,7 @@ enum OmniType {
10
10
  Contact = 'contact',
11
11
  }
12
12
 
13
- interface OmniOption {
13
+ export interface OmniOption {
14
14
  id: string;
15
15
  name: string;
16
16
  type: OmniType;
@@ -78,6 +78,9 @@ export class Omnibox extends RapidElement {
78
78
  @property({ type: String })
79
79
  label: string;
80
80
 
81
+ @property({ type: String, attribute: 'info_text' })
82
+ infoText = '';
83
+
81
84
  /** An option in the drop down */
82
85
  private renderOption(option: OmniOption): TemplateResult {
83
86
  return html`
@@ -98,7 +101,7 @@ export class Omnibox extends RapidElement {
98
101
 
99
102
  if (option.urn && option.type === OmniType.Contact) {
100
103
  if (option.urn !== option.name) {
101
- return html` <div style=${styleMap(style)}>${option.urn}</div> `;
104
+ return html`<div style=${styleMap(style)}>${option.urn}</div>`;
102
105
  }
103
106
  }
104
107
 
@@ -137,11 +140,11 @@ export class Omnibox extends RapidElement {
137
140
 
138
141
  private getIcon(option: OmniOption): TemplateResult {
139
142
  if (option.type === OmniType.Group) {
140
- return html` <temba-icon name="${Icon.group}" /> `;
143
+ return html`<temba-icon name="${Icon.group}"></temba-icon>`;
141
144
  }
142
145
 
143
146
  if (option.type === OmniType.Contact) {
144
- return html` <temba-icon name="${Icon.contact}" /> `;
147
+ return html`<temba-icon name="${Icon.contact}"></temba-icon>`;
145
148
  }
146
149
  }
147
150
 
@@ -185,9 +188,12 @@ export class Omnibox extends RapidElement {
185
188
  .renderSelectedItem=${this.renderSelection.bind(this)}
186
189
  .inputRoot=${this}
187
190
  .isMatch=${this.isMatch}
191
+ .infoText=${this.infoText}
188
192
  searchable
189
193
  searchOnFocus
190
194
  multi
195
+ ><div slot="right">
196
+ <slot name="right"></slot></div
191
197
  ></temba-select>
192
198
  `;
193
199
  }
@@ -599,14 +599,16 @@ export class Select extends FormElement {
599
599
  this.selection = this.values[0];
600
600
  this.value = this.serializeValue(this.values[0]);
601
601
  } else {
602
- this.values.forEach(value => {
603
- const ele = document.createElement('input');
604
- ele.setAttribute('type', 'hidden');
605
- ele.setAttribute('name', name);
606
- ele.setAttribute('value', this.serializeValue(value));
607
- this.hiddenInputs.push(ele);
608
- this.inputRoot.parentElement.appendChild(ele);
609
- });
602
+ if (this.inputRoot.parentElement) {
603
+ this.values.forEach(value => {
604
+ const ele = document.createElement('input');
605
+ ele.setAttribute('type', 'hidden');
606
+ ele.setAttribute('name', name);
607
+ ele.setAttribute('value', this.serializeValue(value));
608
+ this.hiddenInputs.push(ele);
609
+ this.inputRoot.parentElement.appendChild(ele);
610
+ });
611
+ }
610
612
  }
611
613
  }
612
614
  }
@@ -1217,7 +1219,10 @@ export class Select extends FormElement {
1217
1219
  }
1218
1220
 
1219
1221
  public removeValue(valueToRemove: any) {
1220
- this.values = this.values.filter((value: any) => value !== valueToRemove);
1222
+ const idx = this.values.indexOf(valueToRemove);
1223
+ if (idx > -1) {
1224
+ this.values.splice(idx, 1);
1225
+ }
1221
1226
  this.requestUpdate('values');
1222
1227
  }
1223
1228
 
@@ -1329,7 +1334,10 @@ export class Select extends FormElement {
1329
1334
  this.handleRemoveSelection(selected);
1330
1335
  }}
1331
1336
  >
1332
- <temba-icon name="${Icon.delete_small}" size="1" />
1337
+ <temba-icon
1338
+ name="${Icon.delete_small}"
1339
+ size="1"
1340
+ ></temba-icon>
1333
1341
  </div>
1334
1342
  `
1335
1343
  : null}
@@ -1339,10 +1347,12 @@ export class Select extends FormElement {
1339
1347
  )}
1340
1348
  ${this.multi ? input : null}
1341
1349
  </div>
1350
+
1342
1351
  </div>
1343
1352
 
1344
1353
  ${clear}
1345
1354
 
1355
+ <slot name="right"></slot>
1346
1356
  ${
1347
1357
  !this.tags
1348
1358
  ? html`<div
@@ -1356,7 +1366,7 @@ export class Select extends FormElement {
1356
1366
  class="select-open ${this.visibleOptions.length > 0
1357
1367
  ? 'open'
1358
1368
  : ''}"
1359
- />
1369
+ ></temba-icon>
1360
1370
  </div>`
1361
1371
  : null
1362
1372
  }