@nyaruka/temba-components 0.169.0 → 0.170.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 +19 -0
- package/dist/locales/es.js +1 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +1 -0
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/pt.js +1 -0
- package/dist/locales/pt.js.map +1 -1
- package/dist/temba-components.js +501 -173
- package/dist/temba-components.js.map +1 -1
- package/package.json +1 -1
- package/src/display/Button.ts +14 -2
- package/src/display/Chat.ts +43 -24
- package/src/display/Dropdown.ts +11 -7
- package/src/display/Label.ts +18 -3
- package/src/display/Options.ts +27 -14
- package/src/display/TembaDate.ts +9 -2
- package/src/form/Compose.ts +37 -28
- package/src/form/select/Select.ts +15 -2
- package/src/interfaces.ts +13 -3
- package/src/list/BroadcastList.ts +20 -7
- package/src/list/ContactList.ts +113 -9
- package/src/list/ContentList.ts +380 -21
- package/src/list/ContentMenu.ts +12 -6
- package/src/list/FieldList.ts +8 -1
- package/src/list/TembaList.ts +99 -29
- package/src/list/TicketList.ts +87 -18
- package/src/live/CampaignEvents.ts +31 -11
- package/src/live/ContactChat.ts +36 -7
- package/src/live/ContactTimeline.ts +46 -24
- package/src/locales/es.ts +1 -0
- package/src/locales/fr.ts +1 -0
- package/src/locales/pt.ts +1 -0
- package/src/simulator/Simulator.ts +1 -0
- package/src/styles/designTokens.ts +57 -17
- package/src/styles/pillVariants.ts +42 -10
- package/static/css/design-system.css +47 -12
- package/static/css/temba-components.css +31 -9
- package/xliff/es.xlf +3 -0
- package/xliff/fr.xlf +3 -0
- package/xliff/pt.xlf +3 -0
package/package.json
CHANGED
package/src/display/Button.ts
CHANGED
|
@@ -101,9 +101,11 @@ export class Button extends LitElement {
|
|
|
101
101
|
background: var(--success, #16a34a);
|
|
102
102
|
color: #fff;
|
|
103
103
|
}
|
|
104
|
+
/* Hover darkening: static pre-mixed values first for browsers
|
|
105
|
+
without color-mix(), re-derived from the token below. */
|
|
104
106
|
.attention-button:hover,
|
|
105
107
|
.affirmative:hover {
|
|
106
|
-
background:
|
|
108
|
+
background: #138f41;
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
/* DS .btn-danger — flat danger fill (no lift). */
|
|
@@ -112,7 +114,17 @@ export class Button extends LitElement {
|
|
|
112
114
|
color: #fff;
|
|
113
115
|
}
|
|
114
116
|
.destructive-button:hover {
|
|
115
|
-
background:
|
|
117
|
+
background: #b73737;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@supports (color: color-mix(in srgb, red, red)) {
|
|
121
|
+
.attention-button:hover,
|
|
122
|
+
.affirmative:hover {
|
|
123
|
+
background: color-mix(in srgb, var(--success, #16a34a) 88%, black);
|
|
124
|
+
}
|
|
125
|
+
.destructive-button:hover {
|
|
126
|
+
background: color-mix(in srgb, var(--danger, #d03f3f) 88%, black);
|
|
127
|
+
}
|
|
116
128
|
}
|
|
117
129
|
|
|
118
130
|
/* DS .btn-ghost — text-only, hover fills with sunken */
|
package/src/display/Chat.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { TemplateResult, html, nothing, PropertyValueMap, css } from 'lit';
|
|
|
2
2
|
import { property } from 'lit/decorators.js';
|
|
3
3
|
import { repeat } from 'lit/directives/repeat.js';
|
|
4
4
|
import { RapidElement } from '../RapidElement';
|
|
5
|
-
import { CustomEventType } from '../interfaces';
|
|
5
|
+
import { CustomEventType, QuickReply } from '../interfaces';
|
|
6
6
|
import { TicketEvent } from '../events';
|
|
7
7
|
import { renderEventSummary } from '../events/eventRenderers';
|
|
8
8
|
import { DEFAULT_AVATAR } from '../webchat/assets';
|
|
@@ -81,7 +81,7 @@ interface User extends ObjectReference {
|
|
|
81
81
|
export interface Msg {
|
|
82
82
|
text: string;
|
|
83
83
|
channel: ObjectReference;
|
|
84
|
-
quick_replies: string[];
|
|
84
|
+
quick_replies: (string | QuickReply)[];
|
|
85
85
|
urn: string;
|
|
86
86
|
direction: string;
|
|
87
87
|
type: string;
|
|
@@ -518,19 +518,15 @@ export class Chat extends RapidElement {
|
|
|
518
518
|
same theme vars the flat fills used — color-mix keeps host
|
|
519
519
|
re-theming working — with a whisper of shadow for depth and
|
|
520
520
|
a hairline border mixed just darker than the fill so bubbles
|
|
521
|
-
hold their edge against the background.
|
|
521
|
+
hold their edge against the background. Base rules carry flat
|
|
522
|
+
fills and pre-mixed borders for browsers without color-mix()
|
|
523
|
+
(pre-Chrome 111); the @supports block below layers the
|
|
524
|
+
gradients on top. */
|
|
522
525
|
.bubble {
|
|
523
526
|
padding: 10px 14px;
|
|
524
|
-
background:
|
|
525
|
-
180deg,
|
|
526
|
-
color-mix(in srgb, var(--color-chat-in, #e5e5ea) 62%, white) 0%,
|
|
527
|
-
var(--color-chat-in, #e5e5ea) 100%
|
|
528
|
-
);
|
|
527
|
+
background: var(--color-chat-in, #e5e5ea);
|
|
529
528
|
border-radius: 18px;
|
|
530
|
-
border: var(
|
|
531
|
-
--chat-border-in,
|
|
532
|
-
1px solid color-mix(in srgb, var(--color-chat-in, #e5e5ea) 93%, black)
|
|
533
|
-
);
|
|
529
|
+
border: var(--chat-border-in, 1px solid #d5d5da);
|
|
534
530
|
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
|
|
535
531
|
}
|
|
536
532
|
|
|
@@ -550,20 +546,40 @@ export class Chat extends RapidElement {
|
|
|
550
546
|
}
|
|
551
547
|
|
|
552
548
|
.incoming .bubble {
|
|
553
|
-
background:
|
|
554
|
-
|
|
555
|
-
color-mix(in srgb, var(--color-chat-out, #007aff) 90%, white) 0%,
|
|
556
|
-
var(--color-chat-out, #007aff) 58%,
|
|
557
|
-
color-mix(in srgb, var(--color-chat-out, #007aff) 95%, black) 100%
|
|
558
|
-
);
|
|
559
|
-
border: var(
|
|
560
|
-
--chat-border-out,
|
|
561
|
-
1px solid
|
|
562
|
-
color-mix(in srgb, var(--color-chat-out, #007aff) 90%, black)
|
|
563
|
-
);
|
|
549
|
+
background: var(--color-chat-out, #007aff);
|
|
550
|
+
border: var(--chat-border-out, 1px solid #006ee6);
|
|
564
551
|
color: white;
|
|
565
552
|
}
|
|
566
553
|
|
|
554
|
+
@supports (color: color-mix(in srgb, red, red)) {
|
|
555
|
+
.bubble {
|
|
556
|
+
background: linear-gradient(
|
|
557
|
+
180deg,
|
|
558
|
+
color-mix(in srgb, var(--color-chat-in, #e5e5ea) 62%, white) 0%,
|
|
559
|
+
var(--color-chat-in, #e5e5ea) 100%
|
|
560
|
+
);
|
|
561
|
+
border: var(
|
|
562
|
+
--chat-border-in,
|
|
563
|
+
1px solid
|
|
564
|
+
color-mix(in srgb, var(--color-chat-in, #e5e5ea) 93%, black)
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.incoming .bubble {
|
|
569
|
+
background: linear-gradient(
|
|
570
|
+
180deg,
|
|
571
|
+
color-mix(in srgb, var(--color-chat-out, #007aff) 90%, white) 0%,
|
|
572
|
+
var(--color-chat-out, #007aff) 58%,
|
|
573
|
+
color-mix(in srgb, var(--color-chat-out, #007aff) 95%, black) 100%
|
|
574
|
+
);
|
|
575
|
+
border: var(
|
|
576
|
+
--chat-border-out,
|
|
577
|
+
1px solid
|
|
578
|
+
color-mix(in srgb, var(--color-chat-out, #007aff) 90%, black)
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
567
583
|
.incoming .latest .bubble {
|
|
568
584
|
border-bottom-right-radius: 4px;
|
|
569
585
|
}
|
|
@@ -573,8 +589,11 @@ export class Chat extends RapidElement {
|
|
|
573
589
|
}
|
|
574
590
|
|
|
575
591
|
/* Notes get the same top-lit gradient treatment as message
|
|
576
|
-
bubbles, derived from their sticky-note yellow.
|
|
592
|
+
bubbles, derived from their sticky-note yellow. The flat fill
|
|
593
|
+
first is the fallback for browsers without color-mix() — all
|
|
594
|
+
literals here, so the invalid gradient drops at parse time. */
|
|
577
595
|
.note .bubble {
|
|
596
|
+
background: #fffac3;
|
|
578
597
|
background: linear-gradient(
|
|
579
598
|
180deg,
|
|
580
599
|
color-mix(in srgb, #fffac3 62%, white) 0%,
|
package/src/display/Dropdown.ts
CHANGED
|
@@ -9,9 +9,6 @@ import { styleMap } from 'lit-html/directives/style-map.js';
|
|
|
9
9
|
export class Dropdown extends RapidElement {
|
|
10
10
|
static get styles() {
|
|
11
11
|
return css`
|
|
12
|
-
:host {
|
|
13
|
-
}
|
|
14
|
-
|
|
15
12
|
.wrapper {
|
|
16
13
|
position: relative;
|
|
17
14
|
}
|
|
@@ -25,9 +22,6 @@ export class Dropdown extends RapidElement {
|
|
|
25
22
|
overflow: auto;
|
|
26
23
|
}
|
|
27
24
|
|
|
28
|
-
.dropdown:focus {
|
|
29
|
-
}
|
|
30
|
-
|
|
31
25
|
.dropdown.dormant {
|
|
32
26
|
height: 0;
|
|
33
27
|
overflow: hidden;
|
|
@@ -35,7 +29,16 @@ export class Dropdown extends RapidElement {
|
|
|
35
29
|
|
|
36
30
|
.dropdown {
|
|
37
31
|
position: fixed;
|
|
38
|
-
|
|
32
|
+
/* open popups sit above floating windows (simulator, 5000) and
|
|
33
|
+
other page chrome, but below dialogs and toasts (10000);
|
|
34
|
+
temba-content-menu mirrors this value on its temba-dropdown
|
|
35
|
+
flex item (see ContentMenu.ts) */
|
|
36
|
+
z-index: 9000;
|
|
37
|
+
/* the dormant (height: 0) rule is applied 250ms after close, so
|
|
38
|
+
until then this stays a laid-out, invisible fixed box at
|
|
39
|
+
z-index 9000 over whatever it was covering — ignore pointer
|
|
40
|
+
events unless we're actually open */
|
|
41
|
+
pointer-events: none;
|
|
39
42
|
padding: 0;
|
|
40
43
|
opacity: 0;
|
|
41
44
|
border-radius: calc(var(--curvature) * 1.5);
|
|
@@ -67,6 +70,7 @@ export class Dropdown extends RapidElement {
|
|
|
67
70
|
|
|
68
71
|
.open .dropdown {
|
|
69
72
|
opacity: 1;
|
|
73
|
+
pointer-events: auto;
|
|
70
74
|
transform: translateY(0.5em) scale(1);
|
|
71
75
|
}
|
|
72
76
|
|
package/src/display/Label.ts
CHANGED
|
@@ -100,7 +100,10 @@ export default class Label extends LitElement {
|
|
|
100
100
|
dark variant shade), so flow stays bluish, group stays purplish,
|
|
101
101
|
etc. — no grey wash. */
|
|
102
102
|
.label[class*='pill-'].clickable .mask:hover {
|
|
103
|
-
|
|
103
|
+
/* Neutral wash first for browsers without color-mix() — the
|
|
104
|
+
currentColor mix can't fall back per-declaration because
|
|
105
|
+
currentColor keeps it valid until computed-value time. */
|
|
106
|
+
background: rgba(0, 0, 0, 0.06);
|
|
104
107
|
}
|
|
105
108
|
.label[class*='pill-'] temba-icon {
|
|
106
109
|
margin-right: 0;
|
|
@@ -121,14 +124,26 @@ export default class Label extends LitElement {
|
|
|
121
124
|
margin: 0;
|
|
122
125
|
border: 0;
|
|
123
126
|
border-radius: 999px;
|
|
124
|
-
background:
|
|
127
|
+
background: rgba(0, 0, 0, 0.12);
|
|
125
128
|
color: inherit;
|
|
126
129
|
opacity: 0.8;
|
|
127
130
|
--icon-color: currentColor;
|
|
128
131
|
}
|
|
129
132
|
.label[class*='pill-'] .remove:hover {
|
|
130
133
|
opacity: 1;
|
|
131
|
-
background:
|
|
134
|
+
background: rgba(0, 0, 0, 0.22);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@supports (color: color-mix(in srgb, red, red)) {
|
|
138
|
+
.label[class*='pill-'].clickable .mask:hover {
|
|
139
|
+
background: color-mix(in oklab, currentColor 10%, transparent);
|
|
140
|
+
}
|
|
141
|
+
.label[class*='pill-'] .remove {
|
|
142
|
+
background: color-mix(in oklab, currentColor 25%, transparent);
|
|
143
|
+
}
|
|
144
|
+
.label[class*='pill-'] .remove:hover {
|
|
145
|
+
background: color-mix(in oklab, currentColor 45%, transparent);
|
|
146
|
+
}
|
|
132
147
|
}
|
|
133
148
|
|
|
134
149
|
/* When a removable X is present, tighten the mask's left padding
|
package/src/display/Options.ts
CHANGED
|
@@ -338,6 +338,13 @@ export class Options extends RapidElement {
|
|
|
338
338
|
@property({ attribute: false })
|
|
339
339
|
renderOption: { (option: any, selected: boolean): TemplateResult };
|
|
340
340
|
|
|
341
|
+
// renders between rows (and above the first) when the data crosses a
|
|
342
|
+
// grouping boundary - the result sits outside the option rows so it never
|
|
343
|
+
// picks up their hover or selection treatment. Returning null means the
|
|
344
|
+
// rows aren't a boundary and nothing is rendered between them.
|
|
345
|
+
@property({ attribute: false })
|
|
346
|
+
renderDivider: { (prev: any, option: any): TemplateResult | null };
|
|
347
|
+
|
|
341
348
|
@property({ attribute: false })
|
|
342
349
|
renderOptionName: { (option: any, selected: boolean): TemplateResult };
|
|
343
350
|
|
|
@@ -816,20 +823,26 @@ export class Options extends RapidElement {
|
|
|
816
823
|
<div class="${classesInner}" style=${styleMap(optionsStyle)}>
|
|
817
824
|
${options.length > 0
|
|
818
825
|
? options.map((option, index) => {
|
|
819
|
-
return html
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
option
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
826
|
+
return html`${this.renderDivider
|
|
827
|
+
? this.renderDivider(
|
|
828
|
+
index > 0 ? options[index - 1] : null,
|
|
829
|
+
option
|
|
830
|
+
)
|
|
831
|
+
: null}
|
|
832
|
+
<div
|
|
833
|
+
data-option-index="${index}"
|
|
834
|
+
@mousemove=${this.handleMouseMove}
|
|
835
|
+
@mousedown=${this.handleOptionClick}
|
|
836
|
+
class="option ${index === this.cursorIndex &&
|
|
837
|
+
!this.internalFocusDisabled
|
|
838
|
+
? 'focused'
|
|
839
|
+
: ''}"
|
|
840
|
+
>
|
|
841
|
+
${this.resolvedRenderOption(
|
|
842
|
+
option,
|
|
843
|
+
index === this.cursorIndex
|
|
844
|
+
)}
|
|
845
|
+
</div>`;
|
|
833
846
|
})
|
|
834
847
|
: this.visible && this.showEmptyMessage
|
|
835
848
|
? html`<div
|
package/src/display/TembaDate.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { css, html, PropertyValues, TemplateResult } from 'lit';
|
|
1
|
+
import { css, html, nothing, PropertyValues, TemplateResult } from 'lit';
|
|
2
2
|
import { property } from 'lit/decorators.js';
|
|
3
3
|
import { RapidElement } from '../RapidElement';
|
|
4
4
|
import { Store } from '../store/Store';
|
|
@@ -47,7 +47,14 @@ export class TembaDate extends RapidElement {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
public render(): TemplateResult {
|
|
50
|
+
public render(): TemplateResult | typeof nothing {
|
|
51
|
+
// unparseable values and zero-value times (e.g. go's zero time,
|
|
52
|
+
// 0001-01-01T00:00:00Z, for a contact that has never been seen) aren't
|
|
53
|
+
// real dates - render nothing rather than something like "2025 years ago"
|
|
54
|
+
if (this.datetime && !(this.datetime.isValid && this.datetime.year > 1)) {
|
|
55
|
+
return nothing;
|
|
56
|
+
}
|
|
57
|
+
|
|
51
58
|
if (this.datetime && this.store) {
|
|
52
59
|
this.datetime.setLocale(this.store.getLocale());
|
|
53
60
|
|
package/src/form/Compose.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { TemplateResult, html, css, PropertyValues, nothing } from 'lit';
|
|
2
2
|
import { FieldElement } from './FieldElement';
|
|
3
3
|
import { property } from 'lit/decorators.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Attachment,
|
|
6
|
+
CustomEventType,
|
|
7
|
+
Language,
|
|
8
|
+
QuickReply,
|
|
9
|
+
Shortcut
|
|
10
|
+
} from '../interfaces';
|
|
5
11
|
import { Icon } from '../Icons';
|
|
6
12
|
import { DEFAULT_MEDIA_ENDPOINT } from '../utils';
|
|
7
13
|
import { Select } from './select/Select';
|
|
@@ -12,7 +18,7 @@ import { setCaretOffset } from '../excellent/caret-utils';
|
|
|
12
18
|
export interface ComposeValue {
|
|
13
19
|
text: string;
|
|
14
20
|
attachments: { uuid: string }[];
|
|
15
|
-
quick_replies: string[];
|
|
21
|
+
quick_replies: (string | QuickReply)[];
|
|
16
22
|
optin: string;
|
|
17
23
|
template: string;
|
|
18
24
|
variables: string[];
|
|
@@ -236,7 +242,7 @@ export class Compose extends FieldElement {
|
|
|
236
242
|
[lang: string]: {
|
|
237
243
|
text: string;
|
|
238
244
|
attachments: Attachment[];
|
|
239
|
-
quick_replies: string[];
|
|
245
|
+
quick_replies: (string | QuickReply)[];
|
|
240
246
|
optin?: { name: string; uuid: string };
|
|
241
247
|
template?: string;
|
|
242
248
|
variables?: string[];
|
|
@@ -357,11 +363,12 @@ export class Compose extends FieldElement {
|
|
|
357
363
|
this.currentText = langValue.text || '';
|
|
358
364
|
this.initialText = langValue.text || '';
|
|
359
365
|
this.currentAttachments = langValue.attachments || [];
|
|
360
|
-
this.currentQuickReplies = (langValue.quick_replies || [])
|
|
361
|
-
(
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
366
|
+
this.currentQuickReplies = (langValue.quick_replies || [])
|
|
367
|
+
.map((reply) =>
|
|
368
|
+
typeof reply === 'string' ? reply : (reply.text ?? null)
|
|
369
|
+
)
|
|
370
|
+
.filter((reply): reply is string => reply !== null)
|
|
371
|
+
.map((value) => ({ name: value, value }));
|
|
365
372
|
this.currentOptin = langValue['optin'] ? [langValue['optin']] : [];
|
|
366
373
|
}
|
|
367
374
|
|
|
@@ -410,25 +417,14 @@ export class Compose extends FieldElement {
|
|
|
410
417
|
// own keydown inserts a newline (which would flash before the send
|
|
411
418
|
// clears it); bubble keeps Enter-to-send working from anywhere else
|
|
412
419
|
// in the compose after inner widgets have had their shot at it
|
|
413
|
-
this.addEventListener(
|
|
414
|
-
|
|
415
|
-
this.handleHostKeyDown as EventListener,
|
|
416
|
-
true
|
|
417
|
-
);
|
|
418
|
-
this.addEventListener('keydown', this.handleHostKeyDown as EventListener);
|
|
420
|
+
this.addEventListener('keydown', this.handleHostKeyDownCapture, true);
|
|
421
|
+
this.addEventListener('keydown', this.handleHostKeyDownBubble);
|
|
419
422
|
}
|
|
420
423
|
|
|
421
424
|
disconnectedCallback() {
|
|
422
425
|
super.disconnectedCallback();
|
|
423
|
-
this.removeEventListener(
|
|
424
|
-
|
|
425
|
-
this.handleHostKeyDown as EventListener,
|
|
426
|
-
true
|
|
427
|
-
);
|
|
428
|
-
this.removeEventListener(
|
|
429
|
-
'keydown',
|
|
430
|
-
this.handleHostKeyDown as EventListener
|
|
431
|
-
);
|
|
426
|
+
this.removeEventListener('keydown', this.handleHostKeyDownCapture, true);
|
|
427
|
+
this.removeEventListener('keydown', this.handleHostKeyDownBubble);
|
|
432
428
|
}
|
|
433
429
|
|
|
434
430
|
private handleShortcutIconClick() {
|
|
@@ -454,7 +450,18 @@ export class Compose extends FieldElement {
|
|
|
454
450
|
});
|
|
455
451
|
}
|
|
456
452
|
|
|
457
|
-
|
|
453
|
+
// events from inside our shadow tree are retargeted to the host, so
|
|
454
|
+
// both registrations see eventPhase as AT_TARGET — the phase can't
|
|
455
|
+
// tell us which registration is firing, so each binds it explicitly
|
|
456
|
+
private handleHostKeyDownCapture = (evt: KeyboardEvent) => {
|
|
457
|
+
this.handleHostKeyDown(evt, true);
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
private handleHostKeyDownBubble = (evt: KeyboardEvent) => {
|
|
461
|
+
this.handleHostKeyDown(evt, false);
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
private handleHostKeyDown = (evt: KeyboardEvent, capture: boolean) => {
|
|
458
465
|
if (evt.key === 'Escape' && this.showShortcuts) {
|
|
459
466
|
evt.preventDefault();
|
|
460
467
|
evt.stopPropagation();
|
|
@@ -474,10 +481,12 @@ export class Compose extends FieldElement {
|
|
|
474
481
|
// during capture only editor presses are claimed — anything else
|
|
475
482
|
// (quick replies, attachments) waits for the bubble so the widget
|
|
476
483
|
// it targets keeps first claim on the event
|
|
477
|
-
if (
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
484
|
+
if (capture && !(editor && evt.composedPath().includes(editor))) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
// by the bubble an inner widget may have claimed the Enter for
|
|
488
|
+
// itself (e.g. the quick reply select adding a tag) — not a send
|
|
489
|
+
if (!capture && evt.defaultPrevented) {
|
|
481
490
|
return;
|
|
482
491
|
}
|
|
483
492
|
if (editor) {
|
|
@@ -371,14 +371,27 @@ export class Select<T extends SelectOption> extends FieldElement {
|
|
|
371
371
|
margin: 0;
|
|
372
372
|
border: 0;
|
|
373
373
|
border-radius: 999px;
|
|
374
|
-
|
|
374
|
+
/* Neutral wash first for browsers without color-mix(); the
|
|
375
|
+
currentColor-tinted version below needs the @supports gate
|
|
376
|
+
because currentColor keeps the declaration valid until
|
|
377
|
+
computed-value time. */
|
|
378
|
+
background: rgba(0, 0, 0, 0.12);
|
|
375
379
|
color: inherit;
|
|
376
380
|
opacity: 0.8;
|
|
377
381
|
--icon-color: currentColor;
|
|
378
382
|
}
|
|
379
383
|
.multi .remove-item:hover {
|
|
380
384
|
opacity: 1;
|
|
381
|
-
background:
|
|
385
|
+
background: rgba(0, 0, 0, 0.22);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
@supports (color: color-mix(in srgb, red, red)) {
|
|
389
|
+
.multi .remove-item {
|
|
390
|
+
background: color-mix(in oklab, currentColor 25%, transparent);
|
|
391
|
+
}
|
|
392
|
+
.multi .remove-item:hover {
|
|
393
|
+
background: color-mix(in oklab, currentColor 45%, transparent);
|
|
394
|
+
}
|
|
382
395
|
}
|
|
383
396
|
|
|
384
397
|
input {
|
package/src/interfaces.ts
CHANGED
|
@@ -47,6 +47,14 @@ export interface Attachment {
|
|
|
47
47
|
error: string;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/** A quick reply as stored/served by the server (engine shape) —
|
|
51
|
+
* `text` buttons carry text, `location` requests may omit it. */
|
|
52
|
+
export interface QuickReply {
|
|
53
|
+
type?: string;
|
|
54
|
+
text?: string;
|
|
55
|
+
extra?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
50
58
|
export enum DateStyle {
|
|
51
59
|
DayFirst = 'day_first',
|
|
52
60
|
MonthFirst = 'month_first',
|
|
@@ -231,8 +239,9 @@ export interface Broadcast {
|
|
|
231
239
|
/** Base-language attachments (`{content_type, url}` objects or
|
|
232
240
|
* `contentType:url` strings). */
|
|
233
241
|
attachments?: (string | Attachment)[];
|
|
234
|
-
/** Base-language quick replies
|
|
235
|
-
|
|
242
|
+
/** Base-language quick replies — engine `{text}` objects, with
|
|
243
|
+
* plain strings tolerated for older data. */
|
|
244
|
+
quick_replies?: (string | QuickReply)[];
|
|
236
245
|
/** The opt-in the broadcast requests, when it is one. */
|
|
237
246
|
optin?: ObjectReference | null;
|
|
238
247
|
/** The WhatsApp template the broadcast sends, when it uses one. */
|
|
@@ -271,7 +280,7 @@ export interface Msg {
|
|
|
271
280
|
text: string;
|
|
272
281
|
status: string;
|
|
273
282
|
channel: ObjectReference;
|
|
274
|
-
quick_replies: string[];
|
|
283
|
+
quick_replies: (string | QuickReply)[];
|
|
275
284
|
urn: string;
|
|
276
285
|
/** The message's contact — populated by the messages CRUDL
|
|
277
286
|
* endpoint. Carries whichever of name/urn the endpoint exposes. */
|
|
@@ -479,6 +488,7 @@ export enum CustomEventType {
|
|
|
479
488
|
StoreUpdated = 'temba-store-updated',
|
|
480
489
|
Ready = 'temba-ready',
|
|
481
490
|
OrderChanged = 'temba-order-changed',
|
|
491
|
+
ColumnOrderChanged = 'temba-column-order-changed',
|
|
482
492
|
DragStart = 'temba-drag-start',
|
|
483
493
|
DragStop = 'temba-drag-stop',
|
|
484
494
|
DragExternal = 'temba-drag-external',
|
|
@@ -198,7 +198,8 @@ export class BroadcastList extends ContentList<Broadcast> {
|
|
|
198
198
|
}
|
|
199
199
|
.menu-button.destructive:hover {
|
|
200
200
|
border-color: #dc2626;
|
|
201
|
-
|
|
201
|
+
/* pre-mixed fallback for browsers without color-mix() */
|
|
202
|
+
background: #fdf2f2;
|
|
202
203
|
}
|
|
203
204
|
.detail-body {
|
|
204
205
|
display: flex;
|
|
@@ -243,15 +244,25 @@ export class BroadcastList extends ContentList<Broadcast> {
|
|
|
243
244
|
align-items: flex-start;
|
|
244
245
|
gap: 0.6em;
|
|
245
246
|
padding: 0.9em 1em 1em;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
var(--sunken, #f1f3f5) 45%,
|
|
249
|
-
var(--surface, #fff)
|
|
250
|
-
);
|
|
247
|
+
/* pre-mixed fallback for browsers without color-mix() */
|
|
248
|
+
background: #f9fafa;
|
|
251
249
|
border: 1px solid var(--border, #e4e7ec);
|
|
252
250
|
border-radius: var(--r);
|
|
253
251
|
line-height: 1.5;
|
|
254
252
|
}
|
|
253
|
+
|
|
254
|
+
@supports (color: color-mix(in srgb, red, red)) {
|
|
255
|
+
.menu-button.destructive:hover {
|
|
256
|
+
background: color-mix(in srgb, #dc2626 6%, var(--surface, #fff));
|
|
257
|
+
}
|
|
258
|
+
.detail-message {
|
|
259
|
+
background: color-mix(
|
|
260
|
+
in srgb,
|
|
261
|
+
var(--sunken, #f1f3f5) 45%,
|
|
262
|
+
var(--surface, #fff)
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
255
266
|
.detail-message temba-expression-highlight {
|
|
256
267
|
align-self: stretch;
|
|
257
268
|
white-space: pre-wrap;
|
|
@@ -872,7 +883,9 @@ export class BroadcastList extends ContentList<Broadcast> {
|
|
|
872
883
|
${broadcast.quick_replies.map(
|
|
873
884
|
(reply) =>
|
|
874
885
|
html`<temba-label type="neutral"
|
|
875
|
-
>${reply
|
|
886
|
+
>${typeof reply === 'string'
|
|
887
|
+
? reply
|
|
888
|
+
: (reply.text ?? reply.type)}</temba-label
|
|
876
889
|
>`
|
|
877
890
|
)}
|
|
878
891
|
</div>`
|