@nyaruka/temba-components 0.113.0 → 0.114.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 (130) hide show
  1. package/CHANGELOG.md +21 -2
  2. package/demo/index.html +1 -1
  3. package/dist/temba-components.js +793 -966
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/aliaseditor/AliasEditor.js.map +1 -1
  6. package/out-tsc/src/button/Button.js +6 -2
  7. package/out-tsc/src/button/Button.js.map +1 -1
  8. package/out-tsc/src/chat/Chat.js +29 -7
  9. package/out-tsc/src/chat/Chat.js.map +1 -1
  10. package/out-tsc/src/compose/Compose.js +10 -5
  11. package/out-tsc/src/compose/Compose.js.map +1 -1
  12. package/out-tsc/src/contacts/ContactChat.js +240 -114
  13. package/out-tsc/src/contacts/ContactChat.js.map +1 -1
  14. package/out-tsc/src/contacts/ContactFieldEditor.js.map +1 -1
  15. package/out-tsc/src/contacts/events.js.map +1 -1
  16. package/out-tsc/src/contacts/helpers.js +5 -1
  17. package/out-tsc/src/contacts/helpers.js.map +1 -1
  18. package/out-tsc/src/contactsearch/ContactSearch.js +1 -1
  19. package/out-tsc/src/contactsearch/ContactSearch.js.map +1 -1
  20. package/out-tsc/src/dropdown/Dropdown.js +121 -108
  21. package/out-tsc/src/dropdown/Dropdown.js.map +1 -1
  22. package/out-tsc/src/interfaces.js +2 -0
  23. package/out-tsc/src/interfaces.js.map +1 -1
  24. package/out-tsc/src/list/ContentMenu.js +11 -8
  25. package/out-tsc/src/list/ContentMenu.js.map +1 -1
  26. package/out-tsc/src/list/RunList.js.map +1 -1
  27. package/out-tsc/src/list/TembaList.js +21 -14
  28. package/out-tsc/src/list/TembaList.js.map +1 -1
  29. package/out-tsc/src/list/TembaMenu.js +11 -12
  30. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  31. package/out-tsc/src/list/TicketList.js +10 -0
  32. package/out-tsc/src/list/TicketList.js.map +1 -1
  33. package/out-tsc/src/omnibox/Omnibox.js +33 -90
  34. package/out-tsc/src/omnibox/Omnibox.js.map +1 -1
  35. package/out-tsc/src/options/Options.js +49 -47
  36. package/out-tsc/src/options/Options.js.map +1 -1
  37. package/out-tsc/src/select/PopupSelect.js +57 -0
  38. package/out-tsc/src/select/PopupSelect.js.map +1 -0
  39. package/out-tsc/src/select/Select.js +194 -144
  40. package/out-tsc/src/select/Select.js.map +1 -1
  41. package/out-tsc/src/select/UserSelect.js +67 -0
  42. package/out-tsc/src/select/UserSelect.js.map +1 -0
  43. package/out-tsc/src/store/Store.js +65 -14
  44. package/out-tsc/src/store/Store.js.map +1 -1
  45. package/out-tsc/src/tabpane/TabPane.js +72 -115
  46. package/out-tsc/src/tabpane/TabPane.js.map +1 -1
  47. package/out-tsc/src/textinput/TextInput.js +1 -0
  48. package/out-tsc/src/textinput/TextInput.js.map +1 -1
  49. package/out-tsc/src/user/TembaUser.js +24 -37
  50. package/out-tsc/src/user/TembaUser.js.map +1 -1
  51. package/out-tsc/src/utils/index.js +13 -6
  52. package/out-tsc/src/utils/index.js.map +1 -1
  53. package/out-tsc/temba-modules.js +4 -2
  54. package/out-tsc/temba-modules.js.map +1 -1
  55. package/out-tsc/test/temba-omnibox.test.js +43 -4
  56. package/out-tsc/test/temba-omnibox.test.js.map +1 -1
  57. package/out-tsc/test/temba-select.test.js +121 -65
  58. package/out-tsc/test/temba-select.test.js.map +1 -1
  59. package/out-tsc/test/utils.test.js +4 -0
  60. package/out-tsc/test/utils.test.js.map +1 -1
  61. package/package.json +1 -1
  62. package/screenshots/truth/compose/attachments-tab.png +0 -0
  63. package/screenshots/truth/compose/attachments-with-files-focused.png +0 -0
  64. package/screenshots/truth/compose/attachments-with-files.png +0 -0
  65. package/screenshots/truth/compose/intial-text.png +0 -0
  66. package/screenshots/truth/compose/no-counter.png +0 -0
  67. package/screenshots/truth/compose/wraps-text-and-spaces.png +0 -0
  68. package/screenshots/truth/compose/wraps-text-and-url.png +0 -0
  69. package/screenshots/truth/compose/wraps-text-no-spaces.png +0 -0
  70. package/screenshots/truth/contacts/chat-failure.png +0 -0
  71. package/screenshots/truth/contacts/chat-for-active-contact.png +0 -0
  72. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  73. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  74. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  75. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  76. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  77. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  78. package/screenshots/truth/content-menu/item-no-buttons.png +0 -0
  79. package/screenshots/truth/content-menu/items-and-buttons.png +0 -0
  80. package/screenshots/truth/omnibox/selected.png +0 -0
  81. package/screenshots/truth/select/enabled-multi-selection.png +0 -0
  82. package/screenshots/truth/select/endpoint-initial-value-updated.png +0 -0
  83. package/screenshots/truth/select/endpoint-initial-value.png +0 -0
  84. package/screenshots/truth/select/expressions.png +0 -0
  85. package/screenshots/truth/select/functions.png +0 -0
  86. package/screenshots/truth/select/initial-value.png +0 -0
  87. package/screenshots/truth/select/multi-with-endpoint.png +0 -0
  88. package/screenshots/truth/select/multiple-initial-values.png +0 -0
  89. package/screenshots/truth/select/selected-multi-test.png +0 -0
  90. package/screenshots/truth/select/static-initial-value.png +0 -0
  91. package/screenshots/truth/select/static-initial-via-selected.png +0 -0
  92. package/screenshots/truth/select/value-initial.png +0 -0
  93. package/src/aliaseditor/AliasEditor.ts +1 -1
  94. package/src/button/Button.ts +6 -2
  95. package/src/chat/Chat.ts +28 -6
  96. package/src/compose/Compose.ts +11 -6
  97. package/src/contacts/ContactChat.ts +260 -118
  98. package/src/contacts/ContactFieldEditor.ts +1 -1
  99. package/src/contacts/events.ts +1 -0
  100. package/src/contacts/helpers.ts +8 -1
  101. package/src/contactsearch/ContactSearch.ts +3 -3
  102. package/src/dropdown/Dropdown.ts +142 -103
  103. package/src/interfaces.ts +4 -1
  104. package/src/list/ContentMenu.ts +11 -9
  105. package/src/list/RunList.ts +3 -1
  106. package/src/list/TembaList.ts +24 -14
  107. package/src/list/TembaMenu.ts +14 -15
  108. package/src/list/TicketList.ts +11 -0
  109. package/src/omnibox/Omnibox.ts +34 -95
  110. package/src/options/Options.ts +57 -60
  111. package/src/select/PopupSelect.ts +53 -0
  112. package/src/select/Select.ts +182 -112
  113. package/src/select/UserSelect.ts +71 -0
  114. package/src/store/Store.ts +70 -21
  115. package/src/tabpane/TabPane.ts +79 -113
  116. package/src/textinput/TextInput.ts +1 -0
  117. package/src/user/TembaUser.ts +30 -41
  118. package/src/utils/index.ts +12 -8
  119. package/temba-modules.ts +4 -2
  120. package/test/temba-omnibox.test.ts +56 -4
  121. package/test/temba-select.test.ts +170 -56
  122. package/test/utils.test.ts +5 -0
  123. package/test-assets/select/omnibox.json +55 -0
  124. package/web-test-runner.config.mjs +16 -4
  125. package/out-tsc/src/contacts/ContactTickets.js +0 -462
  126. package/out-tsc/src/contacts/ContactTickets.js.map +0 -1
  127. package/out-tsc/test/temba-contact-tickets.test.js +0 -36
  128. package/out-tsc/test/temba-contact-tickets.test.js.map +0 -1
  129. package/src/contacts/ContactTickets.ts +0 -490
  130. package/test/temba-contact-tickets.test.ts +0 -52
@@ -1,8 +1,7 @@
1
- import { TemplateResult, html, css } from 'lit';
1
+ import { TemplateResult, html, PropertyValues } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
3
  import { styleMap } from 'lit-html/directives/style-map.js';
4
- import { RapidElement } from '../RapidElement';
5
- import { Select } from '../select/Select';
4
+ import { Select, SelectOption } from '../select/Select';
6
5
  import { Icon } from '../vectoricon';
7
6
 
8
7
  enum OmniType {
@@ -10,7 +9,7 @@ enum OmniType {
10
9
  Contact = 'contact'
11
10
  }
12
11
 
13
- export interface OmniOption {
12
+ export interface OmniOption extends SelectOption {
14
13
  id: string;
15
14
  name: string;
16
15
  type: OmniType;
@@ -26,24 +25,9 @@ const postNameStyle = {
26
25
  fontSize: '12px'
27
26
  };
28
27
 
29
- export class Omnibox extends RapidElement {
30
- static get styles() {
31
- return css`
32
- temba-select:focus {
33
- outline: none;
34
- box-shadow: none;
35
- }
36
-
37
- :host {
38
- }
39
- `;
40
- }
41
-
42
- @property()
43
- endpoint: string;
44
-
45
- @property()
46
- name: string;
28
+ export class Omnibox extends Select<OmniOption> {
29
+ @property({ type: String })
30
+ valueKey = 'uuid';
47
31
 
48
32
  @property({ type: Boolean })
49
33
  groups = false;
@@ -51,38 +35,43 @@ export class Omnibox extends RapidElement {
51
35
  @property({ type: Boolean })
52
36
  contacts = false;
53
37
 
54
- @property({ type: Array })
55
- value: OmniOption[] = [];
56
-
57
- @property({ type: Array })
58
- errors: string[];
59
-
60
- @property()
38
+ @property({ type: String })
61
39
  placeholder = 'Select recipients';
62
40
 
63
41
  @property({ type: Boolean })
64
- disabled = false;
42
+ multi = true;
65
43
 
66
- @property({ type: String, attribute: 'help_text' })
67
- helpText: string;
68
-
69
- @property({ type: Boolean, attribute: 'help_always' })
70
- helpAlways: boolean;
44
+ @property({ type: Boolean })
45
+ searchable = true;
71
46
 
72
- @property({ type: Boolean, attribute: 'widget_only' })
73
- widgetOnly: boolean;
47
+ @property({ type: Boolean })
48
+ searchOnFocus = true;
74
49
 
75
- @property({ type: Boolean, attribute: 'hide_label' })
76
- hideLabel: boolean;
50
+ @property({ type: Boolean })
51
+ queryParam = 'search';
52
+
53
+ public update(changes: PropertyValues): void {
54
+ super.update(changes);
55
+
56
+ if (
57
+ (changes.has('groups') || changes.has('contacts')) &&
58
+ (this.groups || this.contacts)
59
+ ) {
60
+ let types = '&types=';
61
+ if (this.groups) {
62
+ types += 'g';
63
+ }
77
64
 
78
- @property({ type: String })
79
- label: string;
65
+ if (this.contacts) {
66
+ types += 'c';
67
+ }
80
68
 
81
- @property({ type: String, attribute: 'info_text' })
82
- infoText = '';
69
+ this.endpoint = this.endpoint + types;
70
+ }
71
+ }
83
72
 
84
73
  /** An option in the drop down */
85
- private renderOption(option: OmniOption): TemplateResult {
74
+ public renderOptionDefault(option: OmniOption): TemplateResult {
86
75
  return html`
87
76
  <div style="display:flex;">
88
77
  <div style="margin-right: 8px">${this.getIcon(option)}</div>
@@ -115,7 +104,7 @@ export class Omnibox extends RapidElement {
115
104
  }
116
105
 
117
106
  /** Selection in the multi-select select box */
118
- private renderSelection(option: OmniOption): TemplateResult {
107
+ public renderSelectedItemDefault(option: OmniOption): TemplateResult {
119
108
  return html`
120
109
  <div
121
110
  style="flex:1 1 auto; display: flex; align-items: stretch; color: var(--color-text-dark); font-size: 12px;"
@@ -147,54 +136,4 @@ export class Omnibox extends RapidElement {
147
136
  return html`<temba-icon name="${Icon.contact}"></temba-icon>`;
148
137
  }
149
138
  }
150
-
151
- private getEndpoint() {
152
- const endpoint = this.endpoint;
153
- let types = '&types=';
154
- if (this.groups) {
155
- types += 'g';
156
- }
157
-
158
- if (this.contacts) {
159
- types += 'c';
160
- }
161
-
162
- return endpoint + types;
163
- }
164
-
165
- public getValues(): any[] {
166
- const select = this.shadowRoot.querySelector('temba-select') as Select;
167
- return select.values;
168
- }
169
-
170
- public isMatch() {
171
- return true;
172
- }
173
-
174
- public render(): TemplateResult {
175
- return html`
176
- <temba-select
177
- name=${this.name}
178
- endpoint=${this.getEndpoint()}
179
- placeholder=${this.placeholder}
180
- queryParam="search"
181
- .label=${this.label}
182
- .helpText=${this.helpText}
183
- .widgetOnly=${this.widgetOnly}
184
- ?disabled=${this.disabled}
185
- .errors=${this.errors}
186
- .values=${this.value}
187
- .renderOption=${this.renderOption.bind(this)}
188
- .renderSelectedItem=${this.renderSelection.bind(this)}
189
- .inputRoot=${this}
190
- .isMatch=${this.isMatch}
191
- .infoText=${this.infoText}
192
- searchable
193
- searchOnFocus
194
- multi
195
- ><div slot="right">
196
- <slot name="right"></slot></div
197
- ></temba-select>
198
- `;
199
- }
200
139
  }
@@ -3,16 +3,15 @@ import { property } from 'lit/decorators.js';
3
3
  import { CustomEventType } from '../interfaces';
4
4
  import { RapidElement, EventHandler } from '../RapidElement';
5
5
  import { styleMap } from 'lit-html/directives/style-map.js';
6
- import {
7
- getClasses,
8
- getScrollParent,
9
- isElementVisible,
10
- throttle
11
- } from '../utils';
6
+ import { getClasses, getScrollParent, throttle } from '../utils';
12
7
 
13
8
  export class Options extends RapidElement {
14
9
  static get styles() {
15
10
  return css`
11
+ :host {
12
+ --transition-speed: 0;
13
+ }
14
+
16
15
  .options-container {
17
16
  background: var(--color-options-bg);
18
17
  user-select: none;
@@ -21,7 +20,7 @@ export class Options extends RapidElement {
21
20
  margin-top: var(--options-margin-top);
22
21
  display: flex;
23
22
  flex-direction: column;
24
- transform: scaleY(0.5) translateY(-5em);
23
+ // transform: scaleY(0.5) translateY(-5em);
25
24
  transition: transform var(--transition-speed)
26
25
  cubic-bezier(0.71, 0.18, 0.61, 1.33),
27
26
  opacity var(--transition-speed) cubic-bezier(0.71, 0.18, 0.61, 1.33);
@@ -93,12 +92,12 @@ export class Options extends RapidElement {
93
92
  .options {
94
93
  border-radius: var(--curvature-widget);
95
94
  overflow-y: auto;
96
- max-height: 225px;
95
+ max-height: 200px;
97
96
  border: none;
98
97
  }
99
98
 
100
99
  .show {
101
- transform: scaleY(1) translateY(0);
100
+ // transform: scaleY(1) translateY(0);
102
101
  border: 1px solid var(--color-widget-border);
103
102
  opacity: 1;
104
103
  z-index: 1;
@@ -281,14 +280,14 @@ export class Options extends RapidElement {
281
280
 
282
281
  resolvedRenderOption: { (option: any, selected: boolean): TemplateResult };
283
282
 
284
- public firstUpdated() {
283
+ public firstUpdated(changed: Map<string, any>) {
284
+ super.firstUpdated(changed);
285
285
  if (!this.block) {
286
286
  this.scrollParent = getScrollParent(this);
287
287
  this.calculatePosition = this.calculatePosition.bind(this);
288
288
  if (this.scrollParent) {
289
289
  this.scrollParent.addEventListener('scroll', this.calculatePosition);
290
290
  }
291
- this.calculatePosition();
292
291
  }
293
292
 
294
293
  this.resolvedRenderOption = (
@@ -302,9 +301,14 @@ export class Options extends RapidElement {
302
301
  this.scrollParent.removeEventListener('scroll', this.calculatePosition);
303
302
  }
304
303
  }
304
+
305
+ if (this.resizeObserver) {
306
+ this.resizeObserver.disconnect();
307
+ }
305
308
  }
306
309
 
307
- private isFocused() {
310
+ private isFocused(): boolean {
311
+ // TODO: this really doesn't seem right
308
312
  const focused =
309
313
  this.closestElement(document.activeElement.tagName) ===
310
314
  document.activeElement;
@@ -312,9 +316,29 @@ export class Options extends RapidElement {
312
316
  return focused;
313
317
  }
314
318
 
319
+ private resizeObserver: ResizeObserver;
320
+
315
321
  public updated(changed: Map<string, any>) {
316
322
  super.updated(changed);
317
323
 
324
+ if (changed.has('anchorTo') && this.anchorTo) {
325
+ const optionsContainer =
326
+ this.shadowRoot.querySelector('.options-container');
327
+
328
+ if (!this.resizeObserver) {
329
+ this.resizeObserver = new ResizeObserver((entries) => {
330
+ window.requestAnimationFrame((): void | undefined => {
331
+ if (!Array.isArray(entries) || !entries.length) {
332
+ return;
333
+ }
334
+ this.calculatePosition();
335
+ this.adjustWidth();
336
+ });
337
+ });
338
+ this.resizeObserver.observe(optionsContainer);
339
+ }
340
+ }
341
+
318
342
  // if our cursor changed, lets make sure our scrollbox is showing it
319
343
  if (!this.internalFocusDisabled && changed.has('cursorIndex')) {
320
344
  this.fireCustomEvent(CustomEventType.CursorChanged, {
@@ -327,11 +351,12 @@ export class Options extends RapidElement {
327
351
  this.tempOptions = changed.get('options');
328
352
  window.setTimeout(() => {
329
353
  this.tempOptions = [];
330
- }, 300);
354
+ }, 0);
331
355
  }
332
356
  }
333
357
 
334
358
  if (changed.has('options')) {
359
+ this.adjustWidth();
335
360
  this.calculatePosition();
336
361
 
337
362
  // allow scrolls to trigger again
@@ -370,12 +395,6 @@ export class Options extends RapidElement {
370
395
  }
371
396
  }
372
397
  }
373
-
374
- if (changed.has('visible')) {
375
- window.setTimeout(() => {
376
- this.calculatePosition();
377
- }, 100);
378
- }
379
398
  }
380
399
 
381
400
  private renderOptionDefault(option: any, selected: boolean): TemplateResult {
@@ -528,6 +547,16 @@ export class Options extends RapidElement {
528
547
  }
529
548
  }
530
549
 
550
+ private adjustWidth() {
551
+ if (this.anchorTo) {
552
+ const anchorBounds = this.anchorTo.getBoundingClientRect();
553
+ this.width =
554
+ this.staticWidth > 0
555
+ ? this.staticWidth
556
+ : anchorBounds.width - 2 - this.marginHorizontal * 2;
557
+ }
558
+ }
559
+
531
560
  private calculatePosition() {
532
561
  if (this.visible && !this.block) {
533
562
  const optionsBounds = this.shadowRoot
@@ -535,34 +564,16 @@ export class Options extends RapidElement {
535
564
  .getBoundingClientRect();
536
565
 
537
566
  if (this.anchorTo) {
567
+ this.top = 0;
538
568
  const anchorBounds = this.anchorTo.getBoundingClientRect();
539
- const topTop = anchorBounds.top - optionsBounds.height;
540
-
541
- if (this.anchorTo && this.scrollParent) {
542
- if (!isElementVisible(this.anchorTo, this.scrollParent)) {
543
- // this.fireCustomEvent(CustomEventType.Canceled);
544
- }
569
+ if (anchorBounds.bottom + optionsBounds.height > window.innerHeight) {
570
+ this.top = -(optionsBounds.height + anchorBounds.height + 20);
545
571
  }
546
-
547
- if (
548
- topTop > 0 &&
549
- anchorBounds.bottom + optionsBounds.height > window.innerHeight
550
- ) {
551
- this.top = topTop; // + window.pageYOffset;
552
- this.poppedTop = true;
553
- } else {
554
- this.top = anchorBounds.bottom; // + window.pageYOffset;
555
- this.poppedTop = false;
556
- }
557
-
558
572
  this.left = anchorBounds.left;
559
- this.width =
560
- this.staticWidth > 0
561
- ? this.staticWidth
562
- : anchorBounds.width - 2 - this.marginHorizontal * 2;
563
573
 
564
- if (this.anchorRight) {
565
- this.left = anchorBounds.right - this.width;
574
+ // adjust for parent scrolling
575
+ if (this.scrollParent) {
576
+ this.top += -this.scrollParent.scrollTop;
566
577
  }
567
578
  }
568
579
  }
@@ -594,16 +605,7 @@ export class Options extends RapidElement {
594
605
  }
595
606
  }
596
607
 
597
- // we need to swallow mouse down so we don't grab focus
598
- private handleMouseDown(evt: MouseEvent) {
599
- evt.preventDefault();
600
- evt.stopPropagation();
601
- }
602
-
603
608
  private handleOptionClick(evt: MouseEvent) {
604
- evt.preventDefault();
605
- evt.stopPropagation();
606
-
607
609
  const index = (evt.currentTarget as HTMLElement).getAttribute(
608
610
  'data-option-index'
609
611
  );
@@ -631,7 +633,7 @@ export class Options extends RapidElement {
631
633
  };
632
634
 
633
635
  if (this.top) {
634
- containerStyle['top'] = `${this.top}px`;
636
+ containerStyle[`transform`] = `translateY(${this.top}px)`;
635
637
  }
636
638
 
637
639
  if (this.left) {
@@ -668,18 +670,13 @@ export class Options extends RapidElement {
668
670
 
669
671
  return html`
670
672
  <div class=${classes} style=${styleMap(containerStyle)}>
671
- <div
672
- class="options-scroll"
673
- @scroll=${this.handleInnerScroll}
674
- @mousedown=${this.handleMouseDown}
675
- >
673
+ <div class="options-scroll" @scroll=${this.handleInnerScroll}>
676
674
  <div class="${classesInner}" style=${styleMap(optionsStyle)}>
677
675
  ${options.map((option, index) => {
678
676
  return html`<div
679
677
  data-option-index="${index}"
680
678
  @mousemove=${this.handleMouseMove}
681
- @click=${this.handleOptionClick}
682
- @mousedown=${this.handleMouseDown}
679
+ @mousedown=${this.handleOptionClick}
683
680
  class="option ${index === this.cursorIndex &&
684
681
  !this.internalFocusDisabled
685
682
  ? 'focused'
@@ -0,0 +1,53 @@
1
+ import { css, html } from 'lit';
2
+ import { RapidElement } from '../RapidElement';
3
+ import { property } from 'lit/decorators.js';
4
+
5
+ export class PopupSelect extends RapidElement {
6
+ public static styles = css`
7
+ :host {
8
+ }
9
+
10
+ .dropdown {
11
+ background: #fff;
12
+ border-radius: 0.5em;
13
+ padding: 0.15em;
14
+ border-radius: var(--curvature);
15
+ }
16
+
17
+ temba-select {
18
+ width: 250px;
19
+ display: block;
20
+ --color-widget-border: transparent;
21
+ --widget-box-shadow: none;
22
+ }
23
+ `;
24
+
25
+ @property({ type: String })
26
+ placeholder: string = '';
27
+
28
+ @property({ type: String })
29
+ endpoint: string = '';
30
+
31
+ private handleChange() {
32
+ this.blur();
33
+ }
34
+
35
+ public render() {
36
+ return html`
37
+ <div>
38
+ <temba-dropdown>
39
+ <div slot="toggle"><slot name="toggle"></slot></div>
40
+ <div class="dropdown" slot="dropdown">
41
+ <temba-select
42
+ placeholder=${this.placeholder}
43
+ endpoint=${this.endpoint}
44
+ clearable
45
+ searchable
46
+ @change=${this.handleChange}
47
+ ></temba-select>
48
+ </div>
49
+ </temba-dropdown>
50
+ </div>
51
+ `;
52
+ }
53
+ }