@nyaruka/temba-components 0.164.0 → 0.166.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.
- package/CHANGELOG.md +20 -0
- package/DEV_DATA.md +11 -11
- package/README.md +4 -4
- package/TEST_OPTIMIZATION.md +8 -11
- package/dist/locales/es.js +4 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +4 -0
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/pt.js +4 -0
- package/dist/locales/pt.js.map +1 -1
- package/dist/static/svg/index.svg +1 -1
- package/dist/temba-components.js +1983 -865
- package/dist/temba-components.js.map +1 -1
- package/orca/setup.sh +2 -2
- package/package.json +13 -18
- package/scripts/dev-data-sync.mjs +4 -4
- package/src/Icons.ts +3 -2
- package/src/display/Chat.ts +232 -18
- package/src/display/ProgressBar.ts +15 -2
- package/src/form/Compose.ts +31 -1
- package/src/form/ContactSearch.ts +229 -126
- package/src/interfaces.ts +73 -0
- package/src/layout/Card.ts +25 -0
- package/src/layout/CardLayout.ts +3 -3
- package/src/layout/HeaderBar.ts +4 -1
- package/src/list/BroadcastList.ts +912 -0
- package/src/list/ContentList.ts +46 -10
- package/src/list/FieldList.ts +1057 -0
- package/src/list/NotificationList.ts +131 -21
- package/src/list/SortableList.ts +7 -2
- package/src/list/TembaList.ts +19 -5
- package/src/list/TembaMenu.ts +47 -46
- package/src/live/CampaignEvents.ts +33 -19
- package/src/live/ContactChat.ts +190 -18
- package/src/live/ContactDetails.ts +20 -13
- package/src/live/ContactFieldEditor.ts +7 -3
- package/src/live/ContactStoreElement.ts +3 -1
- package/src/live/ContactTimeline.ts +28 -7
- package/src/live/Realtime.ts +133 -0
- package/src/live/SocketService.ts +24 -0
- package/src/locales/es.ts +4 -0
- package/src/locales/fr.ts +4 -0
- package/src/locales/pt.ts +4 -0
- package/src/store/Store.ts +12 -0
- package/src/utils.ts +4 -0
- package/static/svg/index.svg +1 -1
- package/static/svg/packs/2-temba/star-filled.svg +3 -0
- package/static/svg/work/traced/star-filled.svg +1 -0
- package/static/svg/work/used/star-filled.svg +3 -0
- package/stress-test.js +3 -3
- package/temba-modules.ts +4 -0
- package/web-test-runner.config.mjs +1 -1
- package/xliff/es.xlf +12 -0
- package/xliff/fr.xlf +12 -0
- package/xliff/pt.xlf +12 -0
|
@@ -1,31 +1,28 @@
|
|
|
1
|
-
import { css, html, TemplateResult } from 'lit';
|
|
1
|
+
import { css, html, PropertyValues, TemplateResult } from 'lit';
|
|
2
2
|
import { TembaList } from './TembaList';
|
|
3
3
|
import { Options } from '../display/Options';
|
|
4
4
|
import { Icon } from '../Icons';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export?: {
|
|
12
|
-
type: string;
|
|
13
|
-
num_records: number;
|
|
14
|
-
};
|
|
15
|
-
import?: {
|
|
16
|
-
type: string;
|
|
17
|
-
num_records: number;
|
|
18
|
-
};
|
|
19
|
-
incident?: {
|
|
20
|
-
type: string;
|
|
21
|
-
started_on: string;
|
|
22
|
-
ended_on?: string;
|
|
23
|
-
};
|
|
24
|
-
}
|
|
5
|
+
import { CustomEventType, Notification } from '../interfaces';
|
|
6
|
+
import {
|
|
7
|
+
RealtimeSubscription,
|
|
8
|
+
subscribeToNotifications
|
|
9
|
+
} from '../live/Realtime';
|
|
10
|
+
import { deleteRequest } from '../utils';
|
|
25
11
|
|
|
26
12
|
export class NotificationList extends TembaList {
|
|
27
13
|
reverseRefresh = false;
|
|
28
14
|
internalFocusDisabled = true;
|
|
15
|
+
|
|
16
|
+
// fed by socket publications instead of interval polling
|
|
17
|
+
protected pollingEnabled = false;
|
|
18
|
+
|
|
19
|
+
private realtimeSubscription: RealtimeSubscription = null;
|
|
20
|
+
|
|
21
|
+
// publications that arrived before the initial fetch completed - a direct
|
|
22
|
+
// prepend would be clobbered when the fetch lands, so they wait for it
|
|
23
|
+
private pendingPubs: Notification[] = [];
|
|
24
|
+
private fetchedOnce = false;
|
|
25
|
+
private subscribedOnce = false;
|
|
29
26
|
static get styles() {
|
|
30
27
|
return css`
|
|
31
28
|
:host {
|
|
@@ -134,6 +131,119 @@ export class NotificationList extends TembaList {
|
|
|
134
131
|
};
|
|
135
132
|
}
|
|
136
133
|
|
|
134
|
+
public connectedCallback() {
|
|
135
|
+
super.connectedCallback();
|
|
136
|
+
this.realtimeSubscription = subscribeToNotifications(
|
|
137
|
+
(notification) => this.handleNotification(notification),
|
|
138
|
+
() => this.handleSubscribed()
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public disconnectedCallback() {
|
|
143
|
+
super.disconnectedCallback();
|
|
144
|
+
if (this.realtimeSubscription) {
|
|
145
|
+
this.realtimeSubscription.unsubscribe();
|
|
146
|
+
this.realtimeSubscription = null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public willUpdate(changed: PropertyValues): void {
|
|
151
|
+
super.willUpdate(changed);
|
|
152
|
+
if (changed.has('loading') && !this.loading) {
|
|
153
|
+
this.fetchedOnce = true;
|
|
154
|
+
const pending = this.pendingPubs;
|
|
155
|
+
this.pendingPubs = [];
|
|
156
|
+
pending.forEach((notification) => this.prependNotification(notification));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private handleNotification(notification: Notification) {
|
|
161
|
+
// ignore redeliveries of notifications we already have so e.g. the
|
|
162
|
+
// menu's unseen badge only lights for genuinely new ones
|
|
163
|
+
if (
|
|
164
|
+
this.items.some((item) => item.url === notification.url) ||
|
|
165
|
+
this.pendingPubs.some((item) => item.url === notification.url)
|
|
166
|
+
) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// announce arrival, even mid-fetch - e.g. the menu's unseen badge
|
|
171
|
+
this.fireCustomEvent(CustomEventType.NotificationReceived, {
|
|
172
|
+
notification
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (this.loading || !this.fetchedOnce) {
|
|
176
|
+
this.pendingPubs.push(notification);
|
|
177
|
+
} else {
|
|
178
|
+
this.prependNotification(notification);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private prependNotification(notification: Notification) {
|
|
183
|
+
// dedupe by url (our valueKey) and prepend
|
|
184
|
+
this.items = [
|
|
185
|
+
notification,
|
|
186
|
+
...this.items.filter((item) => item.url !== notification.url)
|
|
187
|
+
];
|
|
188
|
+
this.mostRecentItem = notification;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Fires on every (re)subscribe including after reconnects. The initial
|
|
193
|
+
* endpoint fetch covers the first one regardless of which completes first,
|
|
194
|
+
* so only refetch page one on resubscribes, to catch anything missed
|
|
195
|
+
* while offline.
|
|
196
|
+
*/
|
|
197
|
+
private handleSubscribed() {
|
|
198
|
+
if (this.subscribedOnce && this.fetchedOnce) {
|
|
199
|
+
this.refresh();
|
|
200
|
+
}
|
|
201
|
+
this.subscribedOnce = true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// urls marked seen on the server but still shown bold for this viewing
|
|
205
|
+
private seenUrls = new Set<string>();
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Marks everything currently unseen as seen on the server, without any
|
|
209
|
+
* refetching - the socket keeps our items current and seen state is fully
|
|
210
|
+
* known here. Items stay bold for the current viewing and unbold on the
|
|
211
|
+
* next call (e.g. the next popup open). Resolves false if the server
|
|
212
|
+
* didn't record it, in which case nothing advances and the next call
|
|
213
|
+
* retries.
|
|
214
|
+
*/
|
|
215
|
+
public markSeen(): Promise<boolean> {
|
|
216
|
+
// unbold whatever was marked seen last time
|
|
217
|
+
if (this.seenUrls.size > 0) {
|
|
218
|
+
this.items = this.items.map((item) =>
|
|
219
|
+
this.seenUrls.has(item.url) ? { ...item, is_seen: true } : item
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const unseen = this.items.filter((item) => !item.is_seen);
|
|
224
|
+
if (unseen.length === 0 || !this.endpoint) {
|
|
225
|
+
return Promise.resolve(true);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// only advance our seen state if the server actually recorded it,
|
|
229
|
+
// otherwise we'd unbold items the server still considers unseen
|
|
230
|
+
return deleteRequest(this.endpoint)
|
|
231
|
+
.then((response) => {
|
|
232
|
+
if (!response.ok) {
|
|
233
|
+
console.warn(
|
|
234
|
+
`failed marking notifications seen (${response.status})`
|
|
235
|
+
);
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
this.seenUrls = new Set(unseen.map((item) => item.url));
|
|
239
|
+
return true;
|
|
240
|
+
})
|
|
241
|
+
.catch((error) => {
|
|
242
|
+
console.warn('failed marking notifications seen', error);
|
|
243
|
+
return false;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
137
247
|
public renderHeader(): TemplateResult {
|
|
138
248
|
return html`<div class="header">
|
|
139
249
|
<temba-icon name="notification"></temba-icon>
|
package/src/list/SortableList.ts
CHANGED
|
@@ -92,6 +92,7 @@ export class SortableList extends RapidElement {
|
|
|
92
92
|
|
|
93
93
|
draggingIdx = -1;
|
|
94
94
|
draggingEle = null;
|
|
95
|
+
ghostOriginalDisplay = '';
|
|
95
96
|
dropPlaceholder: HTMLDivElement = null;
|
|
96
97
|
pendingDropIndex = -1;
|
|
97
98
|
pendingTargetElement: HTMLElement = null;
|
|
@@ -729,8 +730,11 @@ export class SortableList extends RapidElement {
|
|
|
729
730
|
this.isExternalDrag = true;
|
|
730
731
|
this.hideDropPlaceholder();
|
|
731
732
|
|
|
732
|
-
// hide the ghost element when dragging externally
|
|
733
|
+
// hide the ghost element when dragging externally, remembering its
|
|
734
|
+
// display (inlined from the original's computed style) so re-entry
|
|
735
|
+
// can restore it - forcing 'block' would break flex-laid-out ghosts
|
|
733
736
|
if (this.ghostElement) {
|
|
737
|
+
this.ghostOriginalDisplay = this.ghostElement.style.display;
|
|
734
738
|
this.ghostElement.style.display = 'none';
|
|
735
739
|
}
|
|
736
740
|
|
|
@@ -745,7 +749,8 @@ export class SortableList extends RapidElement {
|
|
|
745
749
|
|
|
746
750
|
// show the ghost element again when dragging internally
|
|
747
751
|
if (this.ghostElement) {
|
|
748
|
-
this.ghostElement.style.display =
|
|
752
|
+
this.ghostElement.style.display =
|
|
753
|
+
this.ghostOriginalDisplay || 'block';
|
|
749
754
|
}
|
|
750
755
|
|
|
751
756
|
this.fireCustomEvent(CustomEventType.DragInternal, {
|
package/src/list/TembaList.ts
CHANGED
|
@@ -68,6 +68,9 @@ export class TembaList extends RapidElement {
|
|
|
68
68
|
|
|
69
69
|
reverseRefresh = true;
|
|
70
70
|
|
|
71
|
+
// subclasses that get realtime updates can opt out of interval polling
|
|
72
|
+
protected pollingEnabled = true;
|
|
73
|
+
|
|
71
74
|
// our next page from our endpoint
|
|
72
75
|
nextPage: string = null;
|
|
73
76
|
|
|
@@ -108,14 +111,17 @@ export class TembaList extends RapidElement {
|
|
|
108
111
|
|
|
109
112
|
public connectedCallback() {
|
|
110
113
|
super.connectedCallback();
|
|
111
|
-
this.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
if (this.pollingEnabled) {
|
|
115
|
+
this.refreshInterval = setInterval(() => {
|
|
116
|
+
if (!this.paused) {
|
|
117
|
+
this.refreshKey = 'default_' + new Date().getTime();
|
|
118
|
+
}
|
|
119
|
+
}, DEFAULT_REFRESH);
|
|
120
|
+
}
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
public disconnectedCallback() {
|
|
124
|
+
super.disconnectedCallback();
|
|
119
125
|
clearInterval(this.refreshInterval);
|
|
120
126
|
}
|
|
121
127
|
|
|
@@ -220,6 +226,14 @@ export class TembaList extends RapidElement {
|
|
|
220
226
|
return this.selected;
|
|
221
227
|
}
|
|
222
228
|
|
|
229
|
+
/** Deselects without firing a change — e.g. mobile going back to the
|
|
230
|
+
* list, so the same row can be selected again. */
|
|
231
|
+
public clearSelection() {
|
|
232
|
+
this.cursorIndex = -1;
|
|
233
|
+
this.selected = null;
|
|
234
|
+
this.value = null;
|
|
235
|
+
}
|
|
236
|
+
|
|
223
237
|
public refresh(): void {
|
|
224
238
|
this.refreshKey = 'requested_' + new Date().getTime();
|
|
225
239
|
}
|
package/src/list/TembaMenu.ts
CHANGED
|
@@ -33,7 +33,6 @@ export interface MenuItem {
|
|
|
33
33
|
avatar?: string;
|
|
34
34
|
trigger?: boolean;
|
|
35
35
|
event?: string;
|
|
36
|
-
mobile?: boolean;
|
|
37
36
|
initial?: string;
|
|
38
37
|
}
|
|
39
38
|
|
|
@@ -401,29 +400,13 @@ export class TembaMenu extends ResizeElement {
|
|
|
401
400
|
flex-direction: row;
|
|
402
401
|
}
|
|
403
402
|
|
|
404
|
-
|
|
403
|
+
/* when collapsed on mobile, the only thing in the header is the
|
|
404
|
+
hamburger — any new direct child of level-0 must opt in with
|
|
405
|
+
the .top class to stay visible here */
|
|
406
|
+
.root.fully-collapsed.mobile .level.level-0 > *:not(.top) {
|
|
405
407
|
display: none;
|
|
406
408
|
}
|
|
407
409
|
|
|
408
|
-
.root.fully-collapsed.mobile .level.level-0 > .empty {
|
|
409
|
-
display: block;
|
|
410
|
-
width: 100%;
|
|
411
|
-
min-width: inherit;
|
|
412
|
-
max-width: inherit;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
.root .level.level-0 > .show-mobile {
|
|
416
|
-
display: none;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
.root.mobile .level.level-0 > .show-mobile {
|
|
420
|
-
display: flex;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
.root.fully-collapsed.mobile .level.level-0 > .show-mobile {
|
|
424
|
-
display: contents !important;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
410
|
.root.fully-collapsed.mobile .level.level-0 .expand-icon {
|
|
428
411
|
max-height: 100%;
|
|
429
412
|
padding: 1em;
|
|
@@ -455,27 +438,12 @@ export class TembaMenu extends ResizeElement {
|
|
|
455
438
|
min-width: inherit;
|
|
456
439
|
}
|
|
457
440
|
|
|
458
|
-
.mobile.fully-collapsed .item {
|
|
459
|
-
}
|
|
460
|
-
|
|
461
441
|
.mobile .expand-icon {
|
|
462
442
|
transition: none;
|
|
463
443
|
transform: rotate(-90deg);
|
|
464
444
|
align-self: center;
|
|
465
445
|
}
|
|
466
446
|
|
|
467
|
-
.mobile.fully-collapsed .level-0 .empty {
|
|
468
|
-
flex-grow: 1;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
.mobile.fully-collapsed .top-spacer {
|
|
472
|
-
flex-grow: 0;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
.mobile.fully-collapsed #dd-workspace {
|
|
476
|
-
display: none;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
447
|
.mobile.fully-collapsed .expand-icon {
|
|
480
448
|
transform: none;
|
|
481
449
|
}
|
|
@@ -819,17 +787,40 @@ export class TembaMenu extends ResizeElement {
|
|
|
819
787
|
}
|
|
820
788
|
|
|
821
789
|
this.loadItems(item);
|
|
822
|
-
|
|
823
|
-
// refresh any embedded lists
|
|
824
|
-
this.shadowRoot
|
|
825
|
-
.querySelectorAll('temba-notification-list')
|
|
826
|
-
.forEach((ele: NotificationList) => {
|
|
827
|
-
ele.refresh();
|
|
828
|
-
});
|
|
829
790
|
}
|
|
830
791
|
|
|
831
792
|
public refresh = debounce(this.doRefresh, 200);
|
|
832
793
|
|
|
794
|
+
/**
|
|
795
|
+
* Opening a popup with notifications in it marks them all seen - the badge
|
|
796
|
+
* clears now, the items unbold next open. No fetching; the socket keeps
|
|
797
|
+
* the list itself current.
|
|
798
|
+
*/
|
|
799
|
+
private handlePopupOpened(event: CustomEvent, menuItem: MenuItem) {
|
|
800
|
+
const dropdown = event.currentTarget as HTMLElement;
|
|
801
|
+
const lists = dropdown.querySelectorAll('temba-notification-list');
|
|
802
|
+
if (lists.length === 0) {
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// clear the badge optimistically, restoring it if marking seen fails so
|
|
807
|
+
// the unseen indicator isn't silently lost
|
|
808
|
+
const bubble = menuItem.bubble;
|
|
809
|
+
if (bubble) {
|
|
810
|
+
menuItem.bubble = null;
|
|
811
|
+
this.requestUpdate('root');
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
lists.forEach((list: NotificationList) => {
|
|
815
|
+
list.markSeen().then((marked) => {
|
|
816
|
+
if (!marked && bubble && !menuItem.bubble) {
|
|
817
|
+
menuItem.bubble = bubble;
|
|
818
|
+
this.requestUpdate('root');
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
|
|
833
824
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
834
825
|
private loadItems(item: MenuItem, event: MouseEvent = null) {
|
|
835
826
|
if (item && item.endpoint) {
|
|
@@ -1143,6 +1134,13 @@ export class TembaMenu extends ResizeElement {
|
|
|
1143
1134
|
if (menuItem.type === 'temba-notification-list') {
|
|
1144
1135
|
return html`<temba-notification-list
|
|
1145
1136
|
endpoint=${menuItem.href}
|
|
1137
|
+
@temba-notification=${() => {
|
|
1138
|
+
// light the unseen badge on our popup item as they arrive
|
|
1139
|
+
if (parent && parent.bubble !== 'tomato') {
|
|
1140
|
+
parent.bubble = 'tomato';
|
|
1141
|
+
this.requestUpdate('root');
|
|
1142
|
+
}
|
|
1143
|
+
}}
|
|
1146
1144
|
></temba-notification-list>`;
|
|
1147
1145
|
}
|
|
1148
1146
|
|
|
@@ -1203,8 +1201,7 @@ export class TembaMenu extends ResizeElement {
|
|
|
1203
1201
|
expanding: this.expanding && this.expanding === menuItem.id,
|
|
1204
1202
|
expanded: this.isExpanded(menuItem),
|
|
1205
1203
|
iconless: !icon && !collapsedIcon && !menuItem.avatar,
|
|
1206
|
-
pressed: this.pressedItem && this.pressedItem.id == menuItem.id
|
|
1207
|
-
'show-mobile': menuItem.mobile
|
|
1204
|
+
pressed: this.pressedItem && this.pressedItem.id == menuItem.id
|
|
1208
1205
|
});
|
|
1209
1206
|
|
|
1210
1207
|
if (menuItem.avatar) {
|
|
@@ -1287,7 +1284,11 @@ export class TembaMenu extends ResizeElement {
|
|
|
1287
1284
|
|
|
1288
1285
|
if (menuItem.popup) {
|
|
1289
1286
|
return html`
|
|
1290
|
-
<temba-dropdown
|
|
1287
|
+
<temba-dropdown
|
|
1288
|
+
id="dd-${menuItem.id}"
|
|
1289
|
+
@temba-opened=${(event: CustomEvent) =>
|
|
1290
|
+
this.handlePopupOpened(event, menuItem)}
|
|
1291
|
+
>
|
|
1291
1292
|
<div slot="toggle">${item}</div>
|
|
1292
1293
|
|
|
1293
1294
|
<div slot="dropdown">
|
|
@@ -94,9 +94,6 @@ export class CampaignEvents extends EndpointMonitorElement {
|
|
|
94
94
|
@property({ type: String })
|
|
95
95
|
lang_scheduled_contacts = 'Contacts scheduled for this event';
|
|
96
96
|
|
|
97
|
-
@property({ type: String })
|
|
98
|
-
lang_okay = 'Okay';
|
|
99
|
-
|
|
100
97
|
@property({ type: String })
|
|
101
98
|
lang_send_message = 'Send Message';
|
|
102
99
|
|
|
@@ -494,14 +491,28 @@ export class CampaignEvents extends EndpointMonitorElement {
|
|
|
494
491
|
line-height: 1.5;
|
|
495
492
|
}
|
|
496
493
|
|
|
497
|
-
/*
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
.detail-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
494
|
+
/* the close (✕) in the header's upper right - the square
|
|
495
|
+
hover-wash button treatment (the pager buttons'), sitting
|
|
496
|
+
just outboard of the action buttons */
|
|
497
|
+
.detail-close {
|
|
498
|
+
flex-shrink: 0;
|
|
499
|
+
margin-left: -4px;
|
|
500
|
+
display: inline-flex;
|
|
501
|
+
align-items: center;
|
|
502
|
+
justify-content: center;
|
|
503
|
+
width: 28px;
|
|
504
|
+
height: 28px;
|
|
505
|
+
border-radius: var(--r-sm);
|
|
506
|
+
color: var(--text-3, #7b8593);
|
|
507
|
+
cursor: pointer;
|
|
508
|
+
user-select: none;
|
|
509
|
+
}
|
|
510
|
+
.detail-close:hover {
|
|
511
|
+
background: var(--sunken);
|
|
512
|
+
color: var(--text-1);
|
|
513
|
+
}
|
|
514
|
+
.detail-close temba-icon {
|
|
515
|
+
--icon-color: currentColor;
|
|
505
516
|
}
|
|
506
517
|
|
|
507
518
|
.detail-section-title {
|
|
@@ -1017,6 +1028,17 @@ export class CampaignEvents extends EndpointMonitorElement {
|
|
|
1017
1028
|
: null}
|
|
1018
1029
|
</div>`
|
|
1019
1030
|
: null}
|
|
1031
|
+
<div
|
|
1032
|
+
class="detail-close"
|
|
1033
|
+
role="button"
|
|
1034
|
+
tabindex="0"
|
|
1035
|
+
aria-label="Close"
|
|
1036
|
+
@click=${this.handleDetailClosed}
|
|
1037
|
+
@keydown=${(e: KeyboardEvent) =>
|
|
1038
|
+
this.handleActivationKey(e, this.handleDetailClosed)}
|
|
1039
|
+
>
|
|
1040
|
+
<temba-icon name=${Icon.close} size="1.4"></temba-icon>
|
|
1041
|
+
</div>
|
|
1020
1042
|
</div>
|
|
1021
1043
|
|
|
1022
1044
|
<div class="detail-body">
|
|
@@ -1072,14 +1094,6 @@ export class CampaignEvents extends EndpointMonitorElement {
|
|
|
1072
1094
|
|
|
1073
1095
|
${this.renderFires()}
|
|
1074
1096
|
</div>
|
|
1075
|
-
|
|
1076
|
-
<div class="detail-footer">
|
|
1077
|
-
<temba-button
|
|
1078
|
-
primary
|
|
1079
|
-
name=${this.lang_okay}
|
|
1080
|
-
@click=${this.handleDetailClosed}
|
|
1081
|
-
></temba-button>
|
|
1082
|
-
</div>
|
|
1083
1097
|
</div>
|
|
1084
1098
|
`
|
|
1085
1099
|
: null}
|