@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
@@ -2,10 +2,16 @@ import { css, html, TemplateResult } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
3
  import { RapidElement } from '../RapidElement';
4
4
  import { getClasses } from '../utils';
5
+ import { CustomEventType } from '../interfaces';
6
+
7
+ import { styleMap } from 'lit-html/directives/style-map.js';
5
8
 
6
9
  export class Dropdown extends RapidElement {
7
10
  static get styles() {
8
11
  return css`
12
+ :host {
13
+ }
14
+
9
15
  .wrapper {
10
16
  position: relative;
11
17
  }
@@ -19,15 +25,20 @@ export class Dropdown extends RapidElement {
19
25
  overflow: auto;
20
26
  }
21
27
 
28
+ .dropdown:focus {
29
+ }
30
+
31
+ .dropdown.dormant {
32
+ pointer-events: none;
33
+ }
34
+
22
35
  .dropdown {
23
- position: absolute;
24
- opacity: 0;
36
+ position: fixed;
25
37
  z-index: 2;
26
- pointer-events: none;
27
38
  padding: 0;
39
+ opacity: 0;
28
40
  border-radius: var(--curvature);
29
41
  background: #fff;
30
- transform: translateY(1em) scale(0.9);
31
42
  transition: all calc(0.8 * var(--transition-speed)) var(--bounce);
32
43
  user-select: none;
33
44
  margin-top: 0px;
@@ -64,7 +75,7 @@ export class Dropdown extends RapidElement {
64
75
  right: 0;
65
76
  bottom: 0;
66
77
  background: rgba(0, 0, 0, 0.7);
67
- opacity: 0;
78
+ opacity: 0.5;
68
79
  transition: opacity var(--transition-speed) ease-in-out;
69
80
  pointer-events: none;
70
81
  z-index: 1;
@@ -85,149 +96,176 @@ export class Dropdown extends RapidElement {
85
96
  open = false;
86
97
 
87
98
  @property({ type: Boolean })
88
- top = false;
89
-
90
- @property({ type: Boolean })
91
- bottom = false;
92
-
93
- @property({ type: Boolean })
94
- left = false;
95
-
96
- @property({ type: Boolean })
97
- right = false;
98
-
99
- @property({ type: Number })
100
- arrowSize = 6;
99
+ dormant = true;
101
100
 
102
101
  @property({ type: Number })
103
- arrowOffset = this.arrowSize * 2;
102
+ arrowSize = 8;
104
103
 
105
104
  @property({ type: Number })
106
- offsetX = 0;
107
-
108
- @property({ type: Number })
109
- offsetY = 0;
105
+ margin = 10;
110
106
 
111
107
  @property({ type: Boolean })
112
108
  mask = false;
113
109
 
110
+ @property({ type: Object, attribute: false })
111
+ dropdownStyle = {};
112
+
113
+ @property({ type: Object, attribute: false })
114
+ arrowStyle = {};
115
+
114
116
  constructor() {
115
117
  super();
116
- this.ensureOnScreen = this.ensureOnScreen.bind(this);
118
+ this.calculatePosition = this.calculatePosition.bind(this);
117
119
  }
118
120
 
121
+ private activeFocus: any;
122
+ private blurHandler: any;
123
+
119
124
  public firstUpdated(props: any) {
120
125
  super.firstUpdated(props);
126
+ this.resetBlurHandler();
127
+ }
121
128
 
122
- const arrow = this.shadowRoot.querySelector('.arrow') as HTMLDivElement;
123
- arrow.style.borderWidth = this.arrowSize + 'px';
124
- arrow.style.top = '-' + this.arrowSize + 'px';
129
+ private resetBlurHandler() {
130
+ const dropdown = this.shadowRoot.querySelector(
131
+ '.dropdown'
132
+ ) as HTMLDivElement;
133
+
134
+ if (this.activeFocus) {
135
+ this.activeFocus.removeEventListener('blur', this.blurHandler);
136
+ }
125
137
 
126
- if (this.arrowOffset < 0) {
127
- arrow.style.right = Math.abs(this.arrowOffset) + 'px';
138
+ this.activeFocus = dropdown;
139
+ this.blurHandler = this.handleBlur.bind(this);
140
+
141
+ this.activeFocus.addEventListener('blur', this.blurHandler);
142
+ }
143
+
144
+ private handleBlur(event: FocusEvent) {
145
+ const newTarget = event.relatedTarget as any;
146
+
147
+ if (this.contains(newTarget)) {
148
+ newTarget.addEventListener('blur', this.blurHandler);
149
+ this.activeFocus = newTarget;
128
150
  } else {
129
- arrow.style.left = this.arrowOffset + 'px';
151
+ this.closeDropdown();
130
152
  }
153
+ }
154
+
155
+ private openDropdown() {
156
+ this.open = true;
157
+ this.dormant = false;
158
+ this.resetBlurHandler();
131
159
 
132
160
  const dropdown = this.shadowRoot.querySelector(
133
161
  '.dropdown'
134
162
  ) as HTMLDivElement;
163
+ dropdown.focus();
164
+ dropdown.click();
135
165
 
136
- dropdown.addEventListener('blur', () => {
137
- // we nest this to deal with clicking the toggle to close
138
- // as we don't want it to toggle an immediate open, probably
139
- // a better way to deal with this
140
- window.setTimeout(() => {
141
- this.open = false;
142
- // blur our host element too
143
- (this.shadowRoot.host as HTMLDivElement).blur();
144
- }, 200);
145
- });
166
+ this.fireCustomEvent(CustomEventType.Opened);
167
+ }
168
+
169
+ private closeDropdown() {
170
+ this.activeFocus.removeEventListener('blur', this.blurHandler);
171
+
172
+ this.open = false;
173
+ this.resetBlurHandler();
174
+
175
+ window.setTimeout(() => {
176
+ this.dormant = true;
177
+ }, 250);
178
+
179
+ this.blur();
146
180
  }
147
181
 
148
182
  public updated(changedProperties: Map<string, any>) {
149
183
  super.updated(changedProperties);
150
- const dropdown = this.shadowRoot.querySelector(
151
- '.dropdown'
152
- ) as HTMLDivElement;
153
184
 
154
- if (changedProperties.has('offsetY') || changedProperties.has('offsetX')) {
155
- dropdown.style.marginTop = this.offsetY + 'px';
156
- if (dropdown.offsetLeft + dropdown.clientWidth > window.outerWidth) {
157
- dropdown.style.marginLeft =
158
- '-' + (dropdown.clientWidth - this.clientWidth - this.offsetX) + 'px';
159
- } else {
160
- if (this.right) {
161
- dropdown.style.marginRight = this.offsetX + 'px';
162
- } else {
163
- dropdown.style.marginLeft = this.offsetX + 'px';
164
- }
165
- }
185
+ if (changedProperties.has('open')) {
186
+ this.dropdownStyle = {};
166
187
  }
167
188
 
168
- if (changedProperties.has('open')) {
169
- // check right away if we are on the screen, and then again moments after render
170
- window.setTimeout(this.ensureOnScreen, 0);
171
- window.setTimeout(this.ensureOnScreen, 100);
189
+ if (changedProperties.has('dropdownStyle')) {
190
+ if (Object.keys(this.dropdownStyle).length === 0) {
191
+ this.calculatePosition();
192
+ }
172
193
  }
173
194
  }
174
195
 
175
- public ensureOnScreen() {
196
+ public calculatePosition() {
176
197
  const dropdown = this.shadowRoot.querySelector(
177
198
  '.dropdown'
178
199
  ) as HTMLDivElement;
200
+ const toggle = this.querySelector('div[slot="toggle"]');
201
+
202
+ const arrow = this.shadowRoot.querySelector('.arrow') as HTMLDivElement;
203
+
204
+ let bumpedUp = false;
205
+ let bumpedLeft = false;
179
206
 
180
- if (dropdown) {
181
- // dropdown will go off the screen, let's push it up
182
- const toggle = this.querySelector('div[slot="toggle"]');
207
+ if (dropdown && toggle) {
208
+ const dropdownBounds = dropdown.getBoundingClientRect();
209
+ const toggleBounds = toggle.getBoundingClientRect();
210
+ const arrowBounds = arrow.getBoundingClientRect();
183
211
 
184
212
  if (!toggle) {
185
213
  return;
186
214
  }
187
215
 
188
- if (dropdown.getBoundingClientRect().bottom > window.innerHeight - 100) {
189
- if (this.bottom) {
190
- dropdown.style.top = toggle.clientHeight + 'px';
191
- } else {
192
- dropdown.style.top = '';
193
- dropdown.style.bottom = toggle.clientHeight + 'px';
194
- }
195
- } else if (dropdown.getBoundingClientRect().top < 0) {
196
- if (this.bottom) {
197
- dropdown.style.top = toggle.clientHeight + 'px';
198
- } else {
199
- dropdown.style.top = toggle.clientHeight + 'px';
200
- dropdown.style.bottom = '';
201
- }
216
+ const dropdownStyle = {
217
+ border: '1px solid rgba(0,0,0,0.1)',
218
+ marginTop: '0.5em'
219
+ };
220
+
221
+ // if off the the right, bump it left
222
+ if (dropdownBounds.right > window.innerWidth) {
223
+ dropdownStyle['left'] =
224
+ toggleBounds.right - dropdownBounds.width + 'px';
225
+ delete dropdownStyle['right'];
226
+ bumpedLeft = true;
202
227
  }
203
228
 
204
- if (dropdown.getBoundingClientRect().right > window.innerWidth) {
205
- dropdown.style.left = '';
206
- dropdown.style.right = '0px';
207
- } else if (dropdown.getBoundingClientRect().left < 0) {
208
- dropdown.style.left = 0 + 'px';
209
- dropdown.style.right = '';
229
+ // if off to the bottom, bump it up
230
+ if (dropdownBounds.bottom > window.innerHeight) {
231
+ dropdownStyle['top'] = toggleBounds.top - dropdownBounds.height + 'px';
232
+ dropdownStyle['margin-top'] = '-0.5em';
233
+ bumpedUp = true;
210
234
  }
211
- }
212
- }
213
235
 
214
- public handleToggleClicked(event: MouseEvent): void {
215
- event.preventDefault();
216
- event.stopPropagation();
217
- if (!this.open) {
218
- this.open = true;
236
+ const arrowStyle = {
237
+ left: toggleBounds.width / 2 - arrowBounds.width / 2 + 'px',
238
+ borderWidth: this.arrowSize + 'px',
239
+ top: '-' + this.arrowSize + 'px'
240
+ };
241
+
242
+ if (bumpedUp) {
243
+ // rotate our arrow 180 degrees
244
+ arrowStyle['transform'] = 'rotate(180deg)';
245
+
246
+ // and place it at the bottom of the dropdown
247
+ arrowStyle['top'] = 'auto';
248
+ arrowStyle['bottom'] = '-' + this.arrowSize + 'px';
249
+ }
219
250
 
220
- const dropdown = this.shadowRoot.querySelector(
221
- '.dropdown'
222
- ) as HTMLDivElement;
223
- dropdown.focus();
251
+ if (bumpedLeft) {
252
+ arrowStyle['right'] =
253
+ toggleBounds.width / 2 - arrowBounds.width / 2 + 'px';
254
+ delete arrowStyle['left'];
255
+ }
256
+
257
+ this.arrowStyle = arrowStyle;
258
+ this.dropdownStyle = dropdownStyle;
224
259
  }
260
+ this.requestUpdate();
225
261
  }
226
262
 
227
- private handleDropdownMouseDown(event: MouseEvent): void {
228
- // block mouse down when clicking inside dropdown so we don't lose focus yet
263
+ public handleToggleClicked(event: MouseEvent): void {
229
264
  event.preventDefault();
230
265
  event.stopPropagation();
266
+ if (!this.open && this.dormant) {
267
+ this.openDropdown();
268
+ }
231
269
  }
232
270
 
233
271
  public render(): TemplateResult {
@@ -236,7 +274,11 @@ export class Dropdown extends RapidElement {
236
274
  ? html`<div class="mask ${this.open ? 'open' : ''}" />`
237
275
  : null}
238
276
 
239
- <div class="wrapper ${this.open ? 'open' : ''}">
277
+ <div
278
+ class="wrapper ${getClasses({
279
+ open: this.open
280
+ })}"
281
+ >
240
282
  <slot
241
283
  name="toggle"
242
284
  class="toggle"
@@ -245,15 +287,12 @@ export class Dropdown extends RapidElement {
245
287
  <div
246
288
  class="${getClasses({
247
289
  dropdown: true,
248
- right: this.right,
249
- left: this.left,
250
- top: this.top,
251
- bottom: this.bottom
290
+ dormant: this.dormant
252
291
  })}"
292
+ style=${styleMap(this.dropdownStyle)}
253
293
  tabindex="0"
254
- @mousedown=${this.handleDropdownMouseDown}
255
294
  >
256
- <div class="arrow"></div>
295
+ <div class="arrow" style=${styleMap(this.arrowStyle)}></div>
257
296
  <div class="dropdown-wrapper">
258
297
  <slot name="dropdown" tabindex="1"></slot>
259
298
  </div>
package/src/interfaces.ts CHANGED
@@ -56,6 +56,7 @@ export interface User {
56
56
  id?: number;
57
57
  first_name?: string;
58
58
  last_name?: string;
59
+ name?: string;
59
60
  email?: string;
60
61
  role?: string;
61
62
  created_on?: string;
@@ -281,5 +282,7 @@ export enum CustomEventType {
281
282
  Resized = 'temba-resized',
282
283
  DetailsChanged = 'temba-details-changed',
283
284
  Error = 'temba-error',
284
- Interrupt = 'temba-interrupt'
285
+ Interrupt = 'temba-interrupt',
286
+ Opened = 'temba-opened',
287
+ TicketUpdated = 'temba-ticket-updated'
285
288
  }
@@ -34,10 +34,14 @@ export enum ContentMenuItemType {
34
34
  export class ContentMenu extends RapidElement {
35
35
  static get styles() {
36
36
  return css`
37
+ :host {
38
+ tabindex: 0;
39
+ }
37
40
  .container {
38
41
  --button-y: 0.4em;
39
42
  --button-x: 1em;
40
43
  display: flex;
44
+ align-items: center;
41
45
  }
42
46
 
43
47
  .button_item,
@@ -45,10 +49,12 @@ export class ContentMenu extends RapidElement {
45
49
  margin-left: 1rem;
46
50
  }
47
51
 
52
+ temba-button {
53
+ margin-right: 0.5rem;
54
+ }
48
55
  .toggle {
49
56
  --icon-color: rgb(102, 102, 102);
50
- padding: 0.5rem;
51
- margin-left: 0.5rem;
57
+ padding: 0.4em;
52
58
  }
53
59
 
54
60
  .toggle:hover {
@@ -62,6 +68,7 @@ export class ContentMenu extends RapidElement {
62
68
  color: rgb(45, 45, 45);
63
69
  z-index: 50;
64
70
  min-width: 200px;
71
+ tabindex: 0;
65
72
  }
66
73
 
67
74
  .divider {
@@ -75,6 +82,7 @@ export class ContentMenu extends RapidElement {
75
82
  font-size: 1.1rem;
76
83
  cursor: pointer;
77
84
  font-weight: 400;
85
+ tabindex: 0;
78
86
  }
79
87
 
80
88
  .item:hover {
@@ -145,7 +153,6 @@ export class ContentMenu extends RapidElement {
145
153
 
146
154
  protected updated(changes: Map<string, any>) {
147
155
  super.updated(changes);
148
-
149
156
  if (changes.has('endpoint') || changes.has('legacy')) {
150
157
  this.fetchContentMenu();
151
158
  }
@@ -168,12 +175,7 @@ export class ContentMenu extends RapidElement {
168
175
  </temba-button>`;
169
176
  })}
170
177
  ${this.items && this.items.length > 0
171
- ? html`<temba-dropdown
172
- arrowsize="8"
173
- arrowoffset="${this.arrowTopLeft ? '12' : '-12'}"
174
- offsety="6"
175
- bottom
176
- >
178
+ ? html`<temba-dropdown>
177
179
  <div slot="toggle" class="toggle">
178
180
  <temba-icon name="menu" size="1.5"></temba-icon>
179
181
  </div>
@@ -70,7 +70,9 @@ export class RunList extends TembaList {
70
70
 
71
71
  if (changedProperties.has('results')) {
72
72
  if (this.results) {
73
- const select = this.shadowRoot.querySelector('temba-select') as Select;
73
+ const select = this.shadowRoot.querySelector(
74
+ 'temba-select'
75
+ ) as Select<any>;
74
76
  select.setOptions(this.results);
75
77
  this.resultKeys = this.results.reduce(
76
78
  (current, result) => ({ ...current, [result.key]: result }),
@@ -224,6 +224,10 @@ export class TembaList extends RapidElement {
224
224
  return this.endpoint;
225
225
  }
226
226
 
227
+ protected sanitizeResults(results: any[]): Promise<any[]> {
228
+ return Promise.resolve(results);
229
+ }
230
+
227
231
  /**
228
232
  * Refreshes the first page, updating any found items in our list
229
233
  */
@@ -250,10 +254,12 @@ export class TembaList extends RapidElement {
250
254
  controller
251
255
  );
252
256
 
257
+ const sanitizedResults = await this.sanitizeResults(page.results);
253
258
  const items = [...this.items];
259
+
254
260
  // remove any dupes already in our list
255
- if (page.results) {
256
- page.results.forEach((newOption: any) => {
261
+ if (sanitizedResults) {
262
+ sanitizedResults.forEach((newOption: any) => {
257
263
  if (this.sanitizeOption) {
258
264
  this.sanitizeOption(newOption);
259
265
  }
@@ -268,9 +274,9 @@ export class TembaList extends RapidElement {
268
274
  });
269
275
 
270
276
  // insert our new items at the front
271
- let results = page.results;
277
+ let results = sanitizedResults;
272
278
  if (this.reverseRefresh) {
273
- results = page.results.reverse();
279
+ results = sanitizedResults.reverse();
274
280
  }
275
281
  const newItems = [...results, ...items];
276
282
 
@@ -332,13 +338,15 @@ export class TembaList extends RapidElement {
332
338
 
333
339
  try {
334
340
  const page = await fetchResultsPage(endpoint, controller);
341
+ const sanitizedResults = await this.sanitizeResults(page.results);
342
+
335
343
  // sanitize our options if necessary
336
344
  if (this.sanitizeOption) {
337
- page.results.forEach(this.sanitizeOption);
345
+ sanitizedResults.forEach(this.sanitizeOption);
338
346
  }
339
347
 
340
- if (page.results) {
341
- fetchedItems = fetchedItems.concat(page.results);
348
+ if (sanitizedResults) {
349
+ fetchedItems = fetchedItems.concat(sanitizedResults);
342
350
  }
343
351
 
344
352
  // save our next pages
@@ -427,14 +435,16 @@ export class TembaList extends RapidElement {
427
435
  if (this.nextPage && !this.loading) {
428
436
  this.loading = true;
429
437
  fetchResultsPage(this.nextPage).then((page: ResultsPage) => {
430
- if (this.sanitizeOption) {
431
- page.results.forEach(this.sanitizeOption);
432
- }
438
+ this.sanitizeResults(page.results).then((sanitizedResults) => {
439
+ if (this.sanitizeOption) {
440
+ sanitizedResults.forEach(this.sanitizeOption);
441
+ }
433
442
 
434
- this.items = [...this.items, ...page.results];
435
- this.nextPage = page.next;
436
- this.pages++;
437
- this.loading = false;
443
+ this.items = [...this.items, ...sanitizedResults];
444
+ this.nextPage = page.next;
445
+ this.pages++;
446
+ this.loading = false;
447
+ });
438
448
  });
439
449
  }
440
450
  }
@@ -154,7 +154,7 @@ export class TembaMenu extends ResizeElement {
154
154
  }
155
155
 
156
156
  .level-0 > temba-dropdown .icon-wrapper {
157
- padding: 0.2em 0.4em 0.2em 0.4em;
157
+ padding: 0.2em 0.4em 0.2em 0em;
158
158
  }
159
159
 
160
160
  .level-0 > .item.selected::before,
@@ -202,6 +202,12 @@ export class TembaMenu extends ResizeElement {
202
202
  }
203
203
 
204
204
  temba-dropdown {
205
+ margin: 0 0.25em;
206
+ }
207
+
208
+ temba-dropdown > div[slot='dropdown'] {
209
+ width: 300px;
210
+ overflow: hidden;
205
211
  }
206
212
 
207
213
  temba-dropdown > div[slot='dropdown'] .avatar > .details {
@@ -1200,21 +1206,14 @@ export class TembaMenu extends ResizeElement {
1200
1206
 
1201
1207
  if (menuItem.popup) {
1202
1208
  return html`
1203
- <temba-dropdown
1204
- offsetx="10"
1205
- arrowoffset="8"
1206
- arrowSize="0"
1207
- drop_align="left"
1208
- id="dd-${menuItem.id}"
1209
- >
1209
+ <temba-dropdown id="dd-${menuItem.id}">
1210
1210
  <div slot="toggle">${item}</div>
1211
- <div slot="dropdown" style="width:300px;overflow:hidden;">
1212
- <div style="max-height:400px;overflow-y:auto">
1213
- ${(menuItem.items || []).map((child: MenuItem) => {
1214
- child.level = menuItem.level + 1;
1215
- return html`${this.renderMenuItem(child, menuItem)}`;
1216
- })}
1217
- </div>
1211
+
1212
+ <div slot="dropdown">
1213
+ ${(menuItem.items || []).map((child: MenuItem) => {
1214
+ child.level = menuItem.level + 1;
1215
+ return html`${this.renderMenuItem(child, menuItem)}`;
1216
+ })}
1218
1217
  </div>
1219
1218
  </temba-dropdown>
1220
1219
  `;
@@ -18,6 +18,15 @@ export class TicketList extends TembaList {
18
18
  return this.endpoint;
19
19
  }
20
20
 
21
+ protected sanitizeResults(results: any[]): Promise<any[]> {
22
+ return new Promise<any[]>((resolve) => {
23
+ const contacts: Contact[] = results as Contact[];
24
+ this.store.resolveUsers(contacts, ['ticket.assignee']).then(() => {
25
+ resolve(contacts);
26
+ });
27
+ });
28
+ }
29
+
21
30
  constructor() {
22
31
  super();
23
32
 
@@ -76,7 +85,9 @@ export class TicketList extends TembaList {
76
85
  <div>
77
86
  ${!contact.ticket.closed_on && contact.ticket.assignee
78
87
  ? html`<temba-user
88
+ name=${contact.ticket.assignee.name}
79
89
  email=${contact.ticket.assignee.email}
90
+ avatar=${contact.ticket.assignee.avatar}
80
91
  scale="0.8"
81
92
  ></temba-user>`
82
93
  : null}