@nyaruka/temba-components 0.156.18 → 0.157.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.
- package/CHANGELOG.md +17 -0
- package/dist/temba-components.js +2119 -1617
- package/dist/temba-components.js.map +1 -1
- package/package.json +1 -1
- package/src/display/Button.ts +102 -121
- package/src/display/Chat.ts +74 -9
- package/src/display/Dropdown.ts +11 -0
- package/src/display/Label.ts +154 -2
- package/src/display/LeafletMap.ts +4 -3
- package/src/display/Options.ts +71 -16
- package/src/display/TembaUser.ts +32 -8
- package/src/events/eventRenderers.ts +243 -95
- package/src/excellent/caret-utils.ts +0 -1
- package/src/flow/AutoTranslate.ts +2 -2
- package/src/flow/Editor.ts +4 -4
- package/src/flow/NodeEditor.ts +2 -2
- package/src/flow/NodeTypeSelector.ts +0 -5
- package/src/flow/RevisionsWindow.ts +1 -3
- package/src/flow/actions/set_contact_language.ts +5 -4
- package/src/flow/nodes/shared.ts +14 -0
- package/src/flow/nodes/split_by_llm_categorize.ts +28 -8
- package/src/flow/utils.ts +39 -60
- package/src/form/ArrayEditor.ts +9 -11
- package/src/form/Checkbox.ts +2 -2
- package/src/form/ColorPicker.ts +5 -3
- package/src/form/Compose.ts +1 -1
- package/src/form/FieldElement.ts +8 -8
- package/src/form/KeyValueEditor.ts +4 -4
- package/src/form/MessageEditor.ts +2 -3
- package/src/form/RangePicker.ts +17 -17
- package/src/form/TembaSlider.ts +10 -10
- package/src/form/TemplateEditor.ts +4 -4
- package/src/form/TextInput.ts +19 -1
- package/src/form/select/Omnibox.ts +21 -20
- package/src/form/select/Select.ts +382 -173
- package/src/form/select/WorkspaceSelect.ts +7 -1
- package/src/interfaces.ts +1 -0
- package/src/languages.ts +56 -0
- package/src/layout/Accordion.ts +2 -2
- package/src/layout/Dialog.ts +1 -3
- package/src/layout/Modax.ts +1 -1
- package/src/list/ContentMenu.ts +1 -2
- package/src/list/SortableList.ts +156 -0
- package/src/list/TembaMenu.ts +159 -113
- package/src/live/ContactBadges.ts +2 -1
- package/src/live/ContactChat.ts +62 -45
- package/src/live/ContactDetails.ts +3 -1
- package/src/live/ContactFieldEditor.ts +36 -31
- package/src/live/FieldManager.ts +4 -4
- package/src/store/AppState.ts +3 -21
- package/src/store/Store.ts +0 -29
- package/src/styles/designTokens.ts +158 -0
- package/src/styles/pillVariants.ts +147 -0
- package/static/css/temba-components.css +141 -36
- package/web-dev-server.config.mjs +0 -1
- package/web-test-runner.config.mjs +98 -1
package/package.json
CHANGED
package/src/display/Button.ts
CHANGED
|
@@ -7,170 +7,148 @@ export class Button extends LitElement {
|
|
|
7
7
|
static get styles() {
|
|
8
8
|
return css`
|
|
9
9
|
:host {
|
|
10
|
-
display: flex;
|
|
10
|
+
display: inline-flex;
|
|
11
11
|
align-self: stretch;
|
|
12
|
-
font-family: var(--font
|
|
13
|
-
font-weight: 400;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.small {
|
|
17
|
-
font-size: 0.8em;
|
|
18
|
-
--button-y: 0px;
|
|
19
|
-
--button-x: 0.5em;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.v-2.button-container {
|
|
23
|
-
background: var(--button-bg);
|
|
24
|
-
background-image: var(--button-bg-img);
|
|
25
|
-
color: var(--button-text);
|
|
26
|
-
box-shadow: var(--button-shadow);
|
|
27
|
-
transition: all calc(var(--transition-speed) / 2) ease-in;
|
|
12
|
+
font-family: var(--font);
|
|
28
13
|
}
|
|
29
14
|
|
|
15
|
+
/* DS .btn-sm — sizing, type, transition, shape */
|
|
30
16
|
.button-container {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
17
|
+
display: inline-flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
justify-content: var(--button-justify, center);
|
|
20
|
+
gap: 6px;
|
|
34
21
|
flex-grow: 1;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
22
|
+
height: 28px;
|
|
23
|
+
padding: 0 10px;
|
|
24
|
+
border: 1px solid transparent;
|
|
25
|
+
border-radius: var(--r-sm);
|
|
26
|
+
font-size: 12.5px;
|
|
27
|
+
font-weight: var(--w-regular);
|
|
28
|
+
letter-spacing: -0.005em;
|
|
29
|
+
line-height: 1;
|
|
30
|
+
white-space: nowrap;
|
|
31
|
+
cursor: pointer;
|
|
38
32
|
user-select: none;
|
|
39
33
|
-webkit-user-select: none;
|
|
40
|
-
text-align: center;
|
|
41
|
-
border: var(--button-border, none);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.button-name {
|
|
45
|
-
white-space: nowrap;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.secondary-button:hover .button-mask {
|
|
49
|
-
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.button-mask:hover {
|
|
53
|
-
background: rgba(0, 0, 0, 0.05);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.button-mask:active {
|
|
57
|
-
background: rgba(0, 0, 0, 0.12);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
.button-container:focus {
|
|
61
34
|
outline: none;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
.button-container.secondary-button:focus-visible .button-mask {
|
|
70
|
-
background: transparent;
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
transition:
|
|
37
|
+
background 120ms,
|
|
38
|
+
border-color 120ms,
|
|
39
|
+
color 120ms,
|
|
40
|
+
box-shadow 120ms;
|
|
71
41
|
}
|
|
72
42
|
|
|
73
43
|
.button-mask {
|
|
74
|
-
|
|
75
|
-
border-radius: var(--curvature);
|
|
76
|
-
border: 1px solid transparent;
|
|
77
|
-
transition: var(--transition-speed);
|
|
78
|
-
background: var(--button-mask);
|
|
79
|
-
display: flex;
|
|
44
|
+
display: inline-flex;
|
|
80
45
|
align-items: center;
|
|
46
|
+
gap: 6px;
|
|
47
|
+
line-height: 1;
|
|
81
48
|
}
|
|
82
49
|
|
|
83
|
-
.button-
|
|
84
|
-
|
|
85
|
-
color: rgba(255, 255, 255, 0.45);
|
|
86
|
-
cursor: default;
|
|
50
|
+
.button-name {
|
|
51
|
+
white-space: nowrap;
|
|
87
52
|
}
|
|
88
53
|
|
|
89
|
-
|
|
90
|
-
|
|
54
|
+
/* even smaller variant (compact lists, list-row actions, etc.) */
|
|
55
|
+
.small {
|
|
56
|
+
height: 24px;
|
|
57
|
+
padding: 0 8px;
|
|
58
|
+
font-size: 12px;
|
|
91
59
|
}
|
|
92
60
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
61
|
+
/* DS .btn-primary — solid accent fill with a slightly darker
|
|
62
|
+
1px border so the visible box matches the secondary's
|
|
63
|
+
1px-bordered box. */
|
|
64
|
+
.primary-button {
|
|
65
|
+
background: var(--accent-600);
|
|
66
|
+
border-color: var(--accent-700);
|
|
67
|
+
color: #fff;
|
|
96
68
|
}
|
|
97
|
-
|
|
98
|
-
|
|
69
|
+
.primary-button:hover {
|
|
70
|
+
background: var(--accent-700);
|
|
99
71
|
}
|
|
100
72
|
|
|
101
|
-
.secondary
|
|
102
|
-
|
|
103
|
-
|
|
73
|
+
/* DS .btn-secondary — surface bg with a 1px gray outline. */
|
|
74
|
+
.secondary-button {
|
|
75
|
+
background: var(--surface);
|
|
76
|
+
border-color: var(--border-strong);
|
|
77
|
+
color: var(--text-1);
|
|
104
78
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
border: 1px solid transparent;
|
|
79
|
+
.secondary-button:hover {
|
|
80
|
+
background: var(--sunken);
|
|
108
81
|
}
|
|
109
82
|
|
|
110
|
-
.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
83
|
+
/* affirmative + attention share DS .btn-primary chrome but tint
|
|
84
|
+
green; treat them as solid CTAs. */
|
|
85
|
+
.attention-button,
|
|
86
|
+
.affirmative {
|
|
87
|
+
background: var(--success, #16a34a);
|
|
88
|
+
border-color: color-mix(in srgb, var(--success, #16a34a) 80%, black);
|
|
89
|
+
color: #fff;
|
|
114
90
|
}
|
|
115
|
-
|
|
116
|
-
.
|
|
117
|
-
background: var(--
|
|
118
|
-
color: var(--color-button-primary-text);
|
|
91
|
+
.attention-button:hover,
|
|
92
|
+
.affirmative:hover {
|
|
93
|
+
background: color-mix(in srgb, var(--success, #16a34a) 88%, black);
|
|
119
94
|
}
|
|
120
95
|
|
|
121
|
-
.
|
|
122
|
-
|
|
96
|
+
/* DS .btn-danger */
|
|
97
|
+
.destructive-button {
|
|
98
|
+
background: var(--danger, #d03f3f);
|
|
99
|
+
border-color: color-mix(in srgb, var(--danger, #d03f3f) 80%, black);
|
|
100
|
+
color: #fff;
|
|
123
101
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
background: var(--color-button-light);
|
|
127
|
-
color: var(--color-button-light-text);
|
|
102
|
+
.destructive-button:hover {
|
|
103
|
+
background: color-mix(in srgb, var(--danger, #d03f3f) 88%, black);
|
|
128
104
|
}
|
|
129
105
|
|
|
106
|
+
/* DS .btn-ghost — text-only, hover fills with sunken */
|
|
107
|
+
.light-button,
|
|
130
108
|
.lined-button {
|
|
131
|
-
|
|
132
|
-
color:
|
|
133
|
-
|
|
109
|
+
background: var(--surface);
|
|
110
|
+
border-color: var(--border-strong);
|
|
111
|
+
color: var(--text-1);
|
|
134
112
|
}
|
|
135
|
-
|
|
136
|
-
.lined-button
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.lined-button .button-mask:hover {
|
|
141
|
-
background: rgba(0, 0, 0, 0.03);
|
|
113
|
+
.light-button:hover,
|
|
114
|
+
.lined-button:hover {
|
|
115
|
+
background: var(--sunken);
|
|
142
116
|
}
|
|
143
117
|
|
|
118
|
+
/* icon-only button — square footprint, ghost chrome */
|
|
144
119
|
.icon-button {
|
|
145
|
-
|
|
146
|
-
|
|
120
|
+
width: 28px;
|
|
121
|
+
padding: 0;
|
|
122
|
+
background: transparent;
|
|
123
|
+
border-color: transparent;
|
|
124
|
+
color: var(--text-2);
|
|
147
125
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
126
|
+
.icon-button:hover {
|
|
127
|
+
background: var(--sunken);
|
|
128
|
+
color: var(--text-1);
|
|
151
129
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
background: var(--color-button-attention);
|
|
155
|
-
color: var(--color-button-primary-text);
|
|
130
|
+
.icon-button.small {
|
|
131
|
+
width: 24px;
|
|
156
132
|
}
|
|
157
133
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
134
|
+
/* active = pressed-down look — slightly inset */
|
|
135
|
+
.button-container.active-button {
|
|
136
|
+
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.12);
|
|
161
137
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
background: var(--color-button-destructive);
|
|
165
|
-
color: var(--color-button-destructive-text);
|
|
138
|
+
.secondary-button.active-button {
|
|
139
|
+
background: var(--sunken);
|
|
166
140
|
}
|
|
167
141
|
|
|
168
|
-
|
|
169
|
-
|
|
142
|
+
/* disabled */
|
|
143
|
+
.button-container.disabled-button {
|
|
144
|
+
opacity: 0.45;
|
|
145
|
+
cursor: not-allowed;
|
|
146
|
+
pointer-events: none;
|
|
170
147
|
}
|
|
171
148
|
|
|
172
|
-
|
|
173
|
-
|
|
149
|
+
/* focus ring — keyboard nav only */
|
|
150
|
+
.button-container:focus-visible {
|
|
151
|
+
box-shadow: var(--widget-box-shadow-focused);
|
|
174
152
|
}
|
|
175
153
|
|
|
176
154
|
.submit-animation {
|
|
@@ -278,6 +256,9 @@ export class Button extends LitElement {
|
|
|
278
256
|
(!this.primary &&
|
|
279
257
|
!this.secondary &&
|
|
280
258
|
!this.attention &&
|
|
259
|
+
!this.destructive &&
|
|
260
|
+
!this.lined &&
|
|
261
|
+
!this.light &&
|
|
281
262
|
this.v == 1),
|
|
282
263
|
'secondary-button': this.secondary,
|
|
283
264
|
'disabled-button': this.disabled,
|
|
@@ -286,7 +267,7 @@ export class Button extends LitElement {
|
|
|
286
267
|
'destructive-button': this.destructive,
|
|
287
268
|
'light-button': this.light,
|
|
288
269
|
'lined-button': this.lined,
|
|
289
|
-
'icon-button': !!this.icon,
|
|
270
|
+
'icon-button': !!this.icon && !this.name,
|
|
290
271
|
small: this.small
|
|
291
272
|
})}"
|
|
292
273
|
tabindex="0"
|
package/src/display/Chat.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { property } from 'lit/decorators.js';
|
|
|
3
3
|
import { repeat } from 'lit/directives/repeat.js';
|
|
4
4
|
import { RapidElement } from '../RapidElement';
|
|
5
5
|
import { CustomEventType } from '../interfaces';
|
|
6
|
+
import { TicketEvent } from '../events';
|
|
6
7
|
import { DEFAULT_AVATAR } from '../webchat/assets';
|
|
7
8
|
|
|
8
9
|
const BATCH_TIME_WINDOW = 60 * 60 * 1000;
|
|
@@ -67,6 +68,8 @@ export interface ObjectReference {
|
|
|
67
68
|
interface User extends ObjectReference {
|
|
68
69
|
avatar?: string;
|
|
69
70
|
email: string;
|
|
71
|
+
first_name?: string;
|
|
72
|
+
last_name?: string;
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
export interface Msg {
|
|
@@ -153,6 +156,11 @@ export class Chat extends RapidElement {
|
|
|
153
156
|
left: 0;
|
|
154
157
|
right: 0;
|
|
155
158
|
display: block;
|
|
159
|
+
/* The slot overlays the bottom of the chat history, so clicks
|
|
160
|
+
on the chat scrollbar or messages behind it must pass
|
|
161
|
+
through. Slotted footer content can opt back in with
|
|
162
|
+
pointer-events: auto on its interactive bits. */
|
|
163
|
+
pointer-events: none;
|
|
156
164
|
}
|
|
157
165
|
|
|
158
166
|
.block {
|
|
@@ -307,11 +315,18 @@ export class Chat extends RapidElement {
|
|
|
307
315
|
|
|
308
316
|
.note .bubble {
|
|
309
317
|
background: #fffac3;
|
|
310
|
-
|
|
318
|
+
border: 1px solid #e8d169;
|
|
319
|
+
color: #5d4e1e;
|
|
311
320
|
}
|
|
312
321
|
|
|
313
322
|
.note .bubble .name {
|
|
314
|
-
color: rgba(
|
|
323
|
+
color: rgba(93, 78, 30, 0.65);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.note-time {
|
|
327
|
+
margin-top: 0.35em;
|
|
328
|
+
font-size: 0.9em;
|
|
329
|
+
color: rgba(93, 78, 30, 0.65);
|
|
315
330
|
}
|
|
316
331
|
|
|
317
332
|
.warning .bubble {
|
|
@@ -447,6 +462,9 @@ export class Chat extends RapidElement {
|
|
|
447
462
|
display: none;
|
|
448
463
|
}
|
|
449
464
|
|
|
465
|
+
/* Top/bottom scroll-shadow indicators. Decorative only — they
|
|
466
|
+
must not intercept clicks (would otherwise block the chat
|
|
467
|
+
scrollbar and the bottom edge of the messages area). */
|
|
450
468
|
.messages:before {
|
|
451
469
|
content: '';
|
|
452
470
|
background: radial-gradient(
|
|
@@ -461,6 +479,7 @@ export class Chat extends RapidElement {
|
|
|
461
479
|
width: 100%;
|
|
462
480
|
transition: opacity var(--toggle-speed, 200ms) ease-out;
|
|
463
481
|
z-index: 1;
|
|
482
|
+
pointer-events: none;
|
|
464
483
|
}
|
|
465
484
|
|
|
466
485
|
.messages:after {
|
|
@@ -480,6 +499,7 @@ export class Chat extends RapidElement {
|
|
|
480
499
|
margin-right: 5em;
|
|
481
500
|
transition: opacity var(--toggle-speed, 200ms) ease-out;
|
|
482
501
|
z-index: 1;
|
|
502
|
+
pointer-events: none;
|
|
483
503
|
}
|
|
484
504
|
|
|
485
505
|
.bubble-wrap {
|
|
@@ -620,6 +640,10 @@ export class Chat extends RapidElement {
|
|
|
620
640
|
padding: 0;
|
|
621
641
|
}
|
|
622
642
|
|
|
643
|
+
.event strong {
|
|
644
|
+
font-weight: 500;
|
|
645
|
+
}
|
|
646
|
+
|
|
623
647
|
.collapse {
|
|
624
648
|
}
|
|
625
649
|
|
|
@@ -941,15 +965,19 @@ export class Chat extends RapidElement {
|
|
|
941
965
|
return { same: true };
|
|
942
966
|
}
|
|
943
967
|
|
|
944
|
-
// for type equivalence, treat all non-message types as the same
|
|
968
|
+
// for type equivalence, treat all non-message types as the same.
|
|
969
|
+
// Notes are treated like messages so they group with other notes
|
|
970
|
+
// only when type + author match (and split from non-note events).
|
|
945
971
|
const isMsg1 =
|
|
946
972
|
msg1.type === 'msg_created' ||
|
|
947
973
|
msg1.type === 'msg_received' ||
|
|
948
|
-
msg1.type === 'ivr_created'
|
|
974
|
+
msg1.type === 'ivr_created' ||
|
|
975
|
+
msg1.type === 'ticket_note_added';
|
|
949
976
|
const isMsg2 =
|
|
950
977
|
msg2.type === 'msg_created' ||
|
|
951
978
|
msg2.type === 'msg_received' ||
|
|
952
|
-
msg2.type === 'ivr_created'
|
|
979
|
+
msg2.type === 'ivr_created' ||
|
|
980
|
+
msg2.type === 'ticket_note_added';
|
|
953
981
|
const typeMatch =
|
|
954
982
|
isMsg1 && isMsg2 ? msg1.type === msg2.type : isMsg1 === isMsg2;
|
|
955
983
|
|
|
@@ -1180,7 +1208,8 @@ export class Chat extends RapidElement {
|
|
|
1180
1208
|
const isMessageType =
|
|
1181
1209
|
currentMsg.type === 'msg_received' ||
|
|
1182
1210
|
currentMsg.type === 'msg_created' ||
|
|
1183
|
-
currentMsg.type === 'ivr_created'
|
|
1211
|
+
currentMsg.type === 'ivr_created' ||
|
|
1212
|
+
currentMsg.type === 'ticket_note_added';
|
|
1184
1213
|
const showAvatar =
|
|
1185
1214
|
this.avatars && ((isMessageType && this.agent) || !incoming);
|
|
1186
1215
|
|
|
@@ -1246,13 +1275,18 @@ export class Chat extends RapidElement {
|
|
|
1246
1275
|
const deletedClass = msgEvent._deleted ? 'deleted' : '';
|
|
1247
1276
|
const latestClass = index === msgIds.length - 1 ? 'latest' : '';
|
|
1248
1277
|
const eventClass = msg._rendered ? 'is-event' : '';
|
|
1278
|
+
const noteClass = msg.type === 'ticket_note_added' ? 'note' : '';
|
|
1249
1279
|
const matchClass =
|
|
1250
1280
|
this.highlightMessageUuid === msg.uuid ? 'search-match' : '';
|
|
1251
1281
|
return html`<div
|
|
1252
|
-
class="row message ${statusClass} ${unsendableClass} ${deletedClass} ${latestClass} ${eventClass} ${matchClass}"
|
|
1282
|
+
class="row message ${statusClass} ${unsendableClass} ${deletedClass} ${latestClass} ${eventClass} ${noteClass} ${matchClass}"
|
|
1253
1283
|
data-uuid=${msg.uuid || nothing}
|
|
1254
1284
|
>
|
|
1255
|
-
${this.renderMessage(
|
|
1285
|
+
${this.renderMessage(
|
|
1286
|
+
msg,
|
|
1287
|
+
index == 0 ? name : null,
|
|
1288
|
+
index === msgIds.length - 1
|
|
1289
|
+
)}
|
|
1256
1290
|
</div>`;
|
|
1257
1291
|
}
|
|
1258
1292
|
)}
|
|
@@ -1262,6 +1296,8 @@ export class Chat extends RapidElement {
|
|
|
1262
1296
|
<temba-user
|
|
1263
1297
|
uuid=${currentMsg._user?.uuid}
|
|
1264
1298
|
name=${name}
|
|
1299
|
+
first_name=${currentMsg._user?.first_name}
|
|
1300
|
+
last_name=${currentMsg._user?.last_name}
|
|
1265
1301
|
avatar=${currentMsg._user?.avatar}
|
|
1266
1302
|
?system=${isSystem}
|
|
1267
1303
|
>
|
|
@@ -1278,11 +1314,40 @@ export class Chat extends RapidElement {
|
|
|
1278
1314
|
return { html: resultHtml, timestamp: newLastShownTimestamp };
|
|
1279
1315
|
}
|
|
1280
1316
|
|
|
1281
|
-
private
|
|
1317
|
+
private renderNote(
|
|
1318
|
+
event: TicketEvent,
|
|
1319
|
+
name: string | null,
|
|
1320
|
+
isLast: boolean
|
|
1321
|
+
): TemplateResult {
|
|
1322
|
+
return html`<div class="bubble-wrap">
|
|
1323
|
+
<div class="bubble">
|
|
1324
|
+
${name ? html`<div class="name">${name}</div>` : null}
|
|
1325
|
+
<div style="white-space: pre-wrap;">${event.note}</div>
|
|
1326
|
+
</div>
|
|
1327
|
+
${isLast
|
|
1328
|
+
? html`<div class="note-time">
|
|
1329
|
+
<temba-date
|
|
1330
|
+
value=${event.created_on.toISOString()}
|
|
1331
|
+
display="relative"
|
|
1332
|
+
></temba-date>
|
|
1333
|
+
</div>`
|
|
1334
|
+
: null}
|
|
1335
|
+
</div>`;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
private renderMessage(
|
|
1339
|
+
event: ContactEvent,
|
|
1340
|
+
name = null,
|
|
1341
|
+
isLast = false
|
|
1342
|
+
): TemplateResult {
|
|
1282
1343
|
if (event._rendered) {
|
|
1283
1344
|
return html`<div class="event">${event._rendered.html}</div>`;
|
|
1284
1345
|
}
|
|
1285
1346
|
|
|
1347
|
+
if (event.type === 'ticket_note_added') {
|
|
1348
|
+
return this.renderNote(event as TicketEvent, name, isLast);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1286
1351
|
const message = event as MsgEvent;
|
|
1287
1352
|
|
|
1288
1353
|
// safety check: if msg doesn't exist, return nothing
|
package/src/display/Dropdown.ts
CHANGED
|
@@ -233,6 +233,17 @@ export class Dropdown extends RapidElement {
|
|
|
233
233
|
arrowLeft = 10;
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
// safety: keep dropdown off the viewport's left edge so popups
|
|
237
|
+
// anchored to far-left toggles (e.g. rail items) don't rub against
|
|
238
|
+
// the window edge. Shift the dropdown right and slide the arrow
|
|
239
|
+
// back the same amount so it still points at the toggle.
|
|
240
|
+
const MIN_LEFT = 8;
|
|
241
|
+
if (dropdownBounds.left < MIN_LEFT && !bumpedLeft) {
|
|
242
|
+
const shift = MIN_LEFT - dropdownBounds.left;
|
|
243
|
+
dropdownStyle['left'] = MIN_LEFT + 'px';
|
|
244
|
+
arrowLeft -= shift;
|
|
245
|
+
}
|
|
246
|
+
|
|
236
247
|
const arrowStyle = {
|
|
237
248
|
left: arrowLeft + 'px',
|
|
238
249
|
borderWidth: this.arrowSize + 'px',
|