@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
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TextIt Design System pill color variants — single source of truth.
|
|
5
|
+
*
|
|
6
|
+
* Provides the color triple (background / foreground / border) keyed
|
|
7
|
+
* off `.pill-{type}` plus the JS-side taxonomy (`PILL_TYPES`,
|
|
8
|
+
* `PILL_TYPE_ICONS`, `iconToPillType`) consumed by Select.ts and
|
|
9
|
+
* flow/utils.ts.
|
|
10
|
+
*
|
|
11
|
+
* Variant theming:
|
|
12
|
+
* - .pill-contact / .pill-group: derived from --recipient (a fixed
|
|
13
|
+
* blue), NOT the accent ramp. This keeps recipient pills stable
|
|
14
|
+
* across brand re-theming via --primary-rgb.
|
|
15
|
+
* - .pill-flow, .pill-channel: bg/border derived from --flow /
|
|
16
|
+
* --channel via color-mix; text + icon use the anchor directly.
|
|
17
|
+
* - .pill-field: bg/border are fixed at the Tailwind yellow ramp
|
|
18
|
+
* (yellow-100 / yellow-300) because yellow has too little
|
|
19
|
+
* contrast against white to color-mix into a recognizable swatch.
|
|
20
|
+
* Text uses yellow-900 for readability; the icon uses --field
|
|
21
|
+
* directly, which is the only knob a host page can re-theme.
|
|
22
|
+
* - .pill-neutral / .pill-label / .pill-keyword: greys; not
|
|
23
|
+
* anchor-driven.
|
|
24
|
+
*
|
|
25
|
+
* Shape (height, padding, radius, icon spacing) is the consumer's
|
|
26
|
+
* concern, since pill use-cases differ: Select chips have a remove
|
|
27
|
+
* button on the right, ContactDetails pills are clickable links, etc.
|
|
28
|
+
*
|
|
29
|
+
* To add a new variant: extend `PILL_TYPES`, optionally add an entry
|
|
30
|
+
* to `PILL_TYPE_ICONS` / `ICON_TO_PILL_TYPE`, append a `.pill-{type}`
|
|
31
|
+
* block below, and reference an anchor in `designTokens.ts`.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** Recognized pill variants. Anything outside this set falls back to
|
|
35
|
+
* `pill-neutral` (or is rejected by callers as not a pill at all). */
|
|
36
|
+
export const PILL_TYPES: ReadonlySet<string> = new Set([
|
|
37
|
+
'neutral',
|
|
38
|
+
'flow',
|
|
39
|
+
'group',
|
|
40
|
+
'contact',
|
|
41
|
+
'field',
|
|
42
|
+
'label',
|
|
43
|
+
'keyword',
|
|
44
|
+
'channel',
|
|
45
|
+
'topic'
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
/** Default icon name for each pill variant. Used when a consumer
|
|
49
|
+
* specifies `type` but not `icon` — keeps Omnibox-style options and
|
|
50
|
+
* Django-form-rendered options visually consistent without making the
|
|
51
|
+
* data layer set both fields. */
|
|
52
|
+
export const PILL_TYPE_ICONS: Readonly<Record<string, string>> = {
|
|
53
|
+
group: 'group',
|
|
54
|
+
contact: 'contact',
|
|
55
|
+
field: 'fields',
|
|
56
|
+
flow: 'flow',
|
|
57
|
+
label: 'label',
|
|
58
|
+
topic: 'topic'
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Inverse mapping: icon name (alias or resolved SVG id) → pill type.
|
|
62
|
+
* Both forms are valid since flow-action items pass through either. */
|
|
63
|
+
const ICON_TO_PILL_TYPE: Readonly<Record<string, string>> = {
|
|
64
|
+
flow: 'flow',
|
|
65
|
+
group: 'group',
|
|
66
|
+
contact: 'contact',
|
|
67
|
+
contacts: 'contact',
|
|
68
|
+
field: 'field',
|
|
69
|
+
fields: 'field',
|
|
70
|
+
label: 'label',
|
|
71
|
+
// resolved Icon enum SVG ids
|
|
72
|
+
'users-01': 'group',
|
|
73
|
+
'atom-01': 'group',
|
|
74
|
+
'user-01': 'contact',
|
|
75
|
+
'tag-01': 'label'
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const iconToPillType = (icon?: string): string | undefined => {
|
|
79
|
+
if (!icon) return undefined;
|
|
80
|
+
if (ICON_TO_PILL_TYPE[icon]) return ICON_TO_PILL_TYPE[icon];
|
|
81
|
+
// Legacy alias prefix (e.g. 'group_smart' → 'group').
|
|
82
|
+
if (icon.startsWith('group')) return 'group';
|
|
83
|
+
return undefined;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const pillVariants = css`
|
|
87
|
+
.pill-neutral {
|
|
88
|
+
background: var(--sunken);
|
|
89
|
+
color: var(--text-1);
|
|
90
|
+
border-color: var(--border);
|
|
91
|
+
--icon-color: var(--text-2);
|
|
92
|
+
}
|
|
93
|
+
.pill-flow {
|
|
94
|
+
background: color-mix(in srgb, var(--flow) 12%, white);
|
|
95
|
+
color: var(--flow);
|
|
96
|
+
border-color: color-mix(in srgb, var(--flow) 25%, white);
|
|
97
|
+
--icon-color: var(--flow);
|
|
98
|
+
}
|
|
99
|
+
/* Recipient color — shared by contacts and groups. Anchored to
|
|
100
|
+
--recipient (fixed blue), NOT the accent ramp, so pills keep their
|
|
101
|
+
identity even when the host page re-themes via --primary-rgb. */
|
|
102
|
+
.pill-contact,
|
|
103
|
+
.pill-group {
|
|
104
|
+
background: color-mix(in srgb, var(--recipient) 12%, white);
|
|
105
|
+
color: var(--recipient);
|
|
106
|
+
border-color: color-mix(in srgb, var(--recipient) 25%, white);
|
|
107
|
+
--icon-color: var(--recipient);
|
|
108
|
+
}
|
|
109
|
+
.pill-channel {
|
|
110
|
+
background: color-mix(in srgb, var(--channel) 12%, white);
|
|
111
|
+
color: var(--channel);
|
|
112
|
+
border-color: color-mix(in srgb, var(--channel) 25%, white);
|
|
113
|
+
--icon-color: var(--channel);
|
|
114
|
+
}
|
|
115
|
+
.pill-topic {
|
|
116
|
+
background: color-mix(in srgb, var(--topic) 12%, white);
|
|
117
|
+
color: var(--topic);
|
|
118
|
+
border-color: color-mix(in srgb, var(--topic) 25%, white);
|
|
119
|
+
--icon-color: var(--topic);
|
|
120
|
+
}
|
|
121
|
+
.pill-field {
|
|
122
|
+
/* Yellow has very low contrast against white, so the color-mix
|
|
123
|
+
approach used by other variants washes out at any readable mix
|
|
124
|
+
percentage. We use the Tailwind yellow ramp directly for bg
|
|
125
|
+
(yellow-100) / border (yellow-300) / text (yellow-900). The
|
|
126
|
+
icon hue stays anchored to --field so a host page can still
|
|
127
|
+
re-theme the variant by overriding that one token. */
|
|
128
|
+
background: #fef9c3;
|
|
129
|
+
color: #854d0e;
|
|
130
|
+
border-color: #fde68a;
|
|
131
|
+
--icon-color: var(--field);
|
|
132
|
+
}
|
|
133
|
+
.pill-keyword {
|
|
134
|
+
background: var(--sunken);
|
|
135
|
+
color: var(--text-1);
|
|
136
|
+
border-color: var(--border);
|
|
137
|
+
--icon-color: var(--text-2);
|
|
138
|
+
font-family: var(--font-mono);
|
|
139
|
+
font-size: 11.5px;
|
|
140
|
+
}
|
|
141
|
+
.pill-label {
|
|
142
|
+
background: var(--sunken);
|
|
143
|
+
color: var(--text-2);
|
|
144
|
+
border-color: var(--border);
|
|
145
|
+
--icon-color: var(--text-2);
|
|
146
|
+
}
|
|
147
|
+
`;
|
|
@@ -6,12 +6,88 @@
|
|
|
6
6
|
|
|
7
7
|
html {
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/* ─── TextIt Design System tokens ─────────────────────────────────────
|
|
10
|
+
Single source of truth. Cascades through Shadow DOM into every
|
|
11
|
+
component. Legacy tokens below are aliased to these — keep both in
|
|
12
|
+
sync if the design system evolves. */
|
|
13
|
+
|
|
14
|
+
/* accent ramp — the primary color sits at 400 and the ramp is
|
|
15
|
+
derived from it in both directions via sRGB mixing.
|
|
16
|
+
The anchor reads from --primary-rgb so host pages can re-theme
|
|
17
|
+
the entire ramp by setting e.g. --primary-rgb: 112, 0, 132. */
|
|
18
|
+
--accent: rgb(var(--primary-rgb, 98, 147, 201));
|
|
19
|
+
--accent-50: color-mix(in srgb, var(--accent) 6%, white);
|
|
20
|
+
--accent-100: color-mix(in srgb, var(--accent) 16%, white);
|
|
21
|
+
--accent-200: color-mix(in srgb, var(--accent) 32%, white);
|
|
22
|
+
--accent-300: color-mix(in srgb, var(--accent) 60%, white);
|
|
23
|
+
--accent-400: var(--accent);
|
|
24
|
+
--accent-500: color-mix(in srgb, var(--accent) 90%, black);
|
|
25
|
+
--accent-600: color-mix(in srgb, var(--accent) 80%, black);
|
|
26
|
+
--accent-700: color-mix(in srgb, var(--accent) 65%, black);
|
|
27
|
+
--accent-800: color-mix(in srgb, var(--accent) 50%, black);
|
|
28
|
+
--accent-900: color-mix(in srgb, var(--accent) 35%, black);
|
|
29
|
+
|
|
30
|
+
/* neutrals */
|
|
31
|
+
--bg: #F6F7F9;
|
|
32
|
+
--surface: #FFFFFF;
|
|
33
|
+
--sunken: #F1F3F5;
|
|
34
|
+
--border: #E6E8EC;
|
|
35
|
+
--border-strong: #D2D6DC;
|
|
36
|
+
--text-1: #1A1F26;
|
|
37
|
+
--text-2: #4D5664;
|
|
38
|
+
--text-3: #7B8593;
|
|
39
|
+
--text-4: #A2ABB8;
|
|
40
|
+
|
|
41
|
+
/* status — full set */
|
|
42
|
+
--success: #16A34A;
|
|
43
|
+
--success-bg: #E8F6EE;
|
|
44
|
+
--success-border: #BFE5CD;
|
|
45
|
+
--info: #2563EB;
|
|
46
|
+
--info-bg: #E8F0FE;
|
|
47
|
+
--info-border: #C7D7F8;
|
|
48
|
+
--warning: #B45309;
|
|
49
|
+
--warning-bg: #FDF3E2;
|
|
50
|
+
--warning-border: #F2D9A9;
|
|
51
|
+
--danger: #D03F3F;
|
|
52
|
+
--danger-bg: #FCEBEB;
|
|
53
|
+
--danger-border: #F4C8C8;
|
|
54
|
+
--neutral: #6B7280;
|
|
55
|
+
--neutral-bg: #EEF0F3;
|
|
56
|
+
--neutral-border: #D8DCE2;
|
|
57
|
+
|
|
58
|
+
/* type */
|
|
59
|
+
--font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
60
|
+
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
61
|
+
--w-regular: 400;
|
|
62
|
+
--w-medium: 500;
|
|
63
|
+
--w-semibold: 600;
|
|
64
|
+
--w-bold: 600;
|
|
65
|
+
|
|
66
|
+
/* shape */
|
|
67
|
+
--r: 8px;
|
|
68
|
+
--r-xs: 2px;
|
|
69
|
+
--r-sm: 4px;
|
|
70
|
+
--r-lg: 12px;
|
|
71
|
+
|
|
72
|
+
/* density */
|
|
73
|
+
--row-h: 36px;
|
|
74
|
+
--input-h: 34px;
|
|
75
|
+
--pad: 10px;
|
|
76
|
+
--gap: 14px;
|
|
77
|
+
|
|
78
|
+
/* shadows */
|
|
79
|
+
--shadow-1: 0 1px 1px rgba(15, 22, 36, 0.04), 0 1px 2px rgba(15, 22, 36, 0.04);
|
|
80
|
+
--shadow-2: 0 1px 1px rgba(15, 22, 36, 0.04), 0 4px 12px rgba(15, 22, 36, 0.06);
|
|
81
|
+
--shadow-3: 0 6px 20px rgba(15, 22, 36, 0.10), 0 2px 6px rgba(15, 22, 36, 0.06);
|
|
82
|
+
|
|
83
|
+
/* ─── legacy aliases — point at the DS tokens above ────────────────── */
|
|
84
|
+
|
|
85
|
+
--font-family: var(--font);
|
|
86
|
+
--primary-rgb: 58, 102, 150;
|
|
11
87
|
--secondary-rgb: 140, 51, 140;
|
|
12
88
|
--tertiary-rgb: 135, 202, 35;
|
|
13
89
|
|
|
14
|
-
--focus-rgb:
|
|
90
|
+
--focus-rgb: 91, 156, 229;
|
|
15
91
|
--error-rgb: 255, 99, 71;
|
|
16
92
|
--success-rgb: 102, 186, 104;
|
|
17
93
|
|
|
@@ -23,50 +99,68 @@
|
|
|
23
99
|
--disabled-opacity: 0.6;
|
|
24
100
|
--curvature: 6px;
|
|
25
101
|
--curvature-widget: 6px;
|
|
102
|
+
--focus: rgb(var(--focus-rgb, 91, 156, 229));
|
|
103
|
+
--focus-50: color-mix(in srgb, var(--focus) 12%, white);
|
|
104
|
+
--focus-100: color-mix(in srgb, var(--focus) 24%, white);
|
|
105
|
+
--focus-200: color-mix(in srgb, var(--focus) 40%, white);
|
|
106
|
+
--focus-300: color-mix(in srgb, var(--focus) 60%, white);
|
|
107
|
+
--focus-600: color-mix(in srgb, var(--focus) 60%, black);
|
|
108
|
+
--focus-700: color-mix(in srgb, var(--focus) 45%, black);
|
|
109
|
+
--focus-muted: color-mix(in srgb, var(--focus) 60%, white);
|
|
110
|
+
--focus-halo: 0 0 0 3px
|
|
111
|
+
color-mix(in srgb, var(--focus) 30%, transparent);
|
|
26
112
|
--color-focus: #a4cafe;
|
|
27
113
|
--color-widget-bg: #fff;
|
|
28
114
|
--color-widget-bg-focused: #fff;
|
|
29
115
|
--color-widget-border: rgb(225, 225, 225);
|
|
30
116
|
|
|
31
|
-
--color-options-bg: var(--
|
|
117
|
+
--color-options-bg: var(--surface);
|
|
32
118
|
|
|
33
|
-
/* primary colors
|
|
119
|
+
/* primary colors */
|
|
34
120
|
--color-selection: #f0f6ff;
|
|
35
121
|
--color-success: #3ca96a;
|
|
36
|
-
|
|
37
|
-
--
|
|
38
|
-
--
|
|
122
|
+
--color-row-hover: rgba(var(--selection-light-rgb), 0.4);
|
|
123
|
+
--color-message: #3c92dd;
|
|
124
|
+
--color-available: #00f100;
|
|
125
|
+
|
|
126
|
+
--widget-box-shadow: rgba(-1, -1, 0, 0.1) 0px 1px 7px 0px,
|
|
127
|
+
rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
|
|
128
|
+
--widget-box-shadow-focused: 0 0 0 3px rgba(164, 202, 254, 0.45),
|
|
129
|
+
rgba(0, 0, 0, 0.05) 0px 3px 7px 0px,
|
|
130
|
+
rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
|
|
39
131
|
--widget-box-shadow-focused-error: 0 0 0 3px rgba(var(--error-rgb), 0.3);
|
|
40
132
|
|
|
41
133
|
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
|
42
|
-
--shadow-widget: 0 3px 20px 0 rgba(0, 0, 0, 0.04),
|
|
134
|
+
--shadow-widget: 0 3px 20px 0 rgba(0, 0, 0, 0.04),
|
|
135
|
+
0 1px 2px 0 rgba(0, 0, 0, 0.02);
|
|
43
136
|
|
|
44
137
|
/* page text, borders, widgets */
|
|
45
|
-
--color-text: #
|
|
46
|
-
--color-widget-text: #
|
|
138
|
+
--color-text: #555;
|
|
139
|
+
--color-widget-text: #555;
|
|
47
140
|
--color-borders: rgba(0, 0, 0, 0.07);
|
|
48
|
-
--color-placeholder:
|
|
141
|
+
--color-placeholder: #ccc;
|
|
49
142
|
|
|
50
143
|
/* light colors, panel backgrounds, selection, etc */
|
|
51
144
|
--color-primary-light: #eee;
|
|
52
|
-
--color-secondary-light:
|
|
145
|
+
--color-secondary-light: rgba(var(--secondary-rgb), 0.3);
|
|
53
146
|
|
|
54
|
-
--color-label:
|
|
147
|
+
--color-label: var(--text-1);
|
|
55
148
|
|
|
56
|
-
/* dark colors, nav bar, buttons, etc
|
|
57
|
-
|
|
149
|
+
/* dark colors, nav bar, buttons, etc — default to a ramp step
|
|
150
|
+
so they auto-theme when --primary-rgb changes. Hosts can still
|
|
151
|
+
override the legacy variable directly to break out of the ramp. */
|
|
152
|
+
--color-primary-dark: var(--accent-500);
|
|
58
153
|
--color-secondary-dark: rgb(var(--secondary-rgb));
|
|
59
154
|
|
|
60
155
|
/* light text goes over dark, dark over lights */
|
|
61
156
|
--color-text-light: rgba(255, 255, 255, 1);
|
|
62
|
-
--color-text-dark:
|
|
157
|
+
--color-text-dark: #555;
|
|
63
158
|
--color-text-dark-secondary: rgba(0, 0, 0, 0.25);
|
|
64
159
|
--color-text-help: rgb(120, 120, 120);
|
|
65
160
|
--color-tertiary: rgb(var(--tertiary-rgb));
|
|
66
161
|
|
|
67
162
|
--help-text-size: 0.85em;
|
|
68
163
|
--help-text-margin-left: 0.3em;
|
|
69
|
-
--color-text-help: rgb(120, 120, 120);
|
|
70
164
|
|
|
71
165
|
/* solid overlays with text */
|
|
72
166
|
--color-overlay-dark: rgba(0, 0, 0, 0.2);
|
|
@@ -74,12 +168,13 @@
|
|
|
74
168
|
--color-overlay-light: rgba(0, 0, 0, 0.05);
|
|
75
169
|
--color-overlay-light-text: rgba(0, 0, 0, 0.6);
|
|
76
170
|
|
|
77
|
-
/* links, buttons, and label badges
|
|
78
|
-
|
|
79
|
-
--color-link-primary
|
|
171
|
+
/* links, buttons, and label badges — default to ramp steps so
|
|
172
|
+
they auto-theme; overridable via direct variable setting. */
|
|
173
|
+
--color-link-primary: var(--accent-500);
|
|
174
|
+
--color-link-primary-hover: var(--accent-600);
|
|
80
175
|
--color-link-secondary: rgba(var(--secondary-rgb), 0.8);
|
|
81
176
|
--color-link-secondary-hover: rgba(var(--secondary-rgb), 0.9);
|
|
82
|
-
--color-button-primary: var(--
|
|
177
|
+
--color-button-primary: var(--accent-500);
|
|
83
178
|
--color-button-primary-text: var(--color-text-light);
|
|
84
179
|
--color-button-light: rgb(246, 248, 250);
|
|
85
180
|
--color-button-light-text: rgb(36, 41, 47);
|
|
@@ -89,10 +184,17 @@
|
|
|
89
184
|
--color-button-destructive: rgb(var(--error-rgb));
|
|
90
185
|
--color-button-destructive-text: var(--color-text-light);
|
|
91
186
|
|
|
92
|
-
--color-button-attention: #
|
|
187
|
+
--color-button-attention: #3ca96a;
|
|
93
188
|
|
|
94
189
|
--color-connectors: #95cbef;
|
|
95
190
|
|
|
191
|
+
--color-label-primary: var(--color-primary-dark);
|
|
192
|
+
--color-label-primary-text: var(--color-text-light);
|
|
193
|
+
--color-label-secondary: var(--color-secondary-dark);
|
|
194
|
+
--color-label-secondary-text: var(--color-text-light);
|
|
195
|
+
--color-label-tertiary: var(--color-tertiary);
|
|
196
|
+
--color-label-tertiary-text: var(--color-text-light);
|
|
197
|
+
|
|
96
198
|
--color-nav-unselected: #fff;
|
|
97
199
|
--color-nav-selected-bg: #fff;
|
|
98
200
|
--color-nav-selected-text: var(--color-primary-dark);
|
|
@@ -116,9 +218,10 @@
|
|
|
116
218
|
|
|
117
219
|
--transition-speed: 250ms;
|
|
118
220
|
--event-padding: 0.5em 1em;
|
|
119
|
-
--temba-select-selected-padding:
|
|
120
|
-
--temba-select-selected-line-height:
|
|
121
|
-
--temba-select-selected-font-size:
|
|
221
|
+
--temba-select-selected-padding: 0 var(--pad);
|
|
222
|
+
--temba-select-selected-line-height: 1.4;
|
|
223
|
+
--temba-select-selected-font-size: 13.5px;
|
|
224
|
+
--temba-select-min-height: var(--input-h);
|
|
122
225
|
|
|
123
226
|
--font-size: 14px;
|
|
124
227
|
--button-font-size: 1.125rem;
|
|
@@ -126,23 +229,25 @@
|
|
|
126
229
|
--header-bg: var(--color-primary-dark);
|
|
127
230
|
--header-text: var(--color-text-light);
|
|
128
231
|
|
|
129
|
-
--temba-textinput-padding:
|
|
130
|
-
--temba-textinput-font-size:
|
|
232
|
+
--temba-textinput-padding: 7px var(--pad);
|
|
233
|
+
--temba-textinput-font-size: 13.5px;
|
|
234
|
+
--temba-textinput-min-height: var(--input-h);
|
|
131
235
|
|
|
132
|
-
--options-block-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
|
133
|
-
|
|
134
|
-
--
|
|
236
|
+
--options-block-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
|
237
|
+
0 1px 2px 0 rgba(0, 0, 0, 0.03);
|
|
238
|
+
--options-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
|
239
|
+
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
240
|
+
--dropdown-shadow: rgb(0 0 0 / 15%) 0px 0px 30px,
|
|
241
|
+
rgb(0 0 0 / 12%) 0px 2px 6px;
|
|
135
242
|
|
|
136
243
|
--label-size: 14px;
|
|
244
|
+
--control-margin-bottom: 15px;
|
|
137
245
|
|
|
138
246
|
--menu-padding: 1em;
|
|
139
247
|
|
|
140
248
|
font-size: var(--font-size);
|
|
141
249
|
font-family: var(--font-family);
|
|
142
250
|
|
|
143
|
-
--button-y: 6px;
|
|
144
|
-
--button-x: 14px;
|
|
145
|
-
|
|
146
251
|
--bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|
147
252
|
|
|
148
253
|
--temba-charcount-counts-margin-top: 4px;
|
|
@@ -156,14 +261,14 @@
|
|
|
156
261
|
}
|
|
157
262
|
|
|
158
263
|
temba-button {
|
|
159
|
-
--button-bg: var(--
|
|
264
|
+
--button-bg: var(--accent-500);
|
|
160
265
|
--button-text: var(--color-text-light);
|
|
161
266
|
--button-border: none;
|
|
162
267
|
--button-shadow: var(--widget-box-shadow);
|
|
163
268
|
}
|
|
164
269
|
|
|
165
270
|
temba-button:hover {
|
|
166
|
-
--button-bg-img: linear-gradient(to bottom, rgba(
|
|
271
|
+
--button-bg-img: linear-gradient(to bottom, rgba(255, 255, 255, .1), transparent, transparent);
|
|
167
272
|
}
|
|
168
273
|
|
|
169
274
|
temba-button.active {
|
|
@@ -255,7 +255,6 @@ export default {
|
|
|
255
255
|
'/api/v2/contacts.json': 'contacts.json',
|
|
256
256
|
'/api/v2/optins.json': 'optins.json',
|
|
257
257
|
'/api/v2/topics.json': 'topics.json',
|
|
258
|
-
'/api/v2/languages.json': 'languages.json',
|
|
259
258
|
'/api/v2/workspace.json': 'workspace.json',
|
|
260
259
|
'/api/internal/locations.json': 'locations.json',
|
|
261
260
|
'/api/internal/orgs.json': 'orgs.json',
|
|
@@ -2,6 +2,100 @@
|
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
3
3
|
process.env.PUPPETEER_DISABLE_HEADLESS_WARNING = '1';
|
|
4
4
|
import { puppeteerLauncher } from '@web/test-runner-puppeteer';
|
|
5
|
+
import { defaultReporter, summaryReporter } from '@web/test-runner';
|
|
6
|
+
|
|
7
|
+
// Workaround for a wtr OOM under coverage on this branch. With our
|
|
8
|
+
// volume of instrumented code, the merged istanbul-coverage payload
|
|
9
|
+
// across all 121 test sessions overflows V8's max string length
|
|
10
|
+
// (~512MB) when `getTestCoverage` deep-clones it via
|
|
11
|
+
// `JSON.parse(JSON.stringify(coverages))`. That clone exists only to
|
|
12
|
+
// insulate watch mode from istanbul's in-place mutation of the
|
|
13
|
+
// originals — in non-watch coverage mode the clone is dead weight.
|
|
14
|
+
// The throw is caught inside `onSessionFinished`'s try/catch, which
|
|
15
|
+
// calls `runner.stop(error)` and silently exits 1 (the `console.error`
|
|
16
|
+
// it logs is swallowed by wtr's BufferedLogger before the final
|
|
17
|
+
// reportEnd flush ever runs).
|
|
18
|
+
//
|
|
19
|
+
// We can't import `getTestCoverage` directly (it's a transitive dep
|
|
20
|
+
// not re-exported by `@web/test-runner`), so we shim JSON.parse: when
|
|
21
|
+
// it's handed the sentinel produced by our JSON.stringify shim, it
|
|
22
|
+
// returns the original `coverages` array instead of the stringified
|
|
23
|
+
// copy. The shim is intentionally narrow:
|
|
24
|
+
//
|
|
25
|
+
// - Gated on `WTR_COVERAGE_SHIM` (defaults on when --coverage is in
|
|
26
|
+
// argv; can be force-disabled by setting it to "0") so it never
|
|
27
|
+
// runs in plain `pnpm test`, only in the coverage path.
|
|
28
|
+
// - Sentinel is a runtime-random string, so a fixture/cached body
|
|
29
|
+
// containing the literal can't collide.
|
|
30
|
+
// - `__pendingCoverages` is cleared on every parse — matched OR not —
|
|
31
|
+
// so the ~512MB reference never lingers past one call pair, and
|
|
32
|
+
// mismatched call sequences never silently corrupt other parses.
|
|
33
|
+
// - The shape check `__looksLikeCoverageArray` only matches arrays
|
|
34
|
+
// whose first element is an object keyed by absolute file paths
|
|
35
|
+
// pointing at istanbul-shaped entries; it can't be triggered by
|
|
36
|
+
// ordinary JSON the orchestrator stringifies (HTTP responses, etc.)
|
|
37
|
+
//
|
|
38
|
+
// A wtr update that changes how getTestCoverage clones would make the
|
|
39
|
+
// shim a no-op (`__looksLikeCoverageArray` returns false) — we'd then
|
|
40
|
+
// regress to the original OOM rather than silently corrupt data, and
|
|
41
|
+
// the next coverage run would surface it immediately. Long-term, the
|
|
42
|
+
// right fix is patch-package on `@web/test-runner-core`'s
|
|
43
|
+
// `getTestCoverage` to skip the clone in non-watch mode; this shim is
|
|
44
|
+
// scoped to keep that scope-creep out of this PR.
|
|
45
|
+
const __WTR_COVERAGE_SHIM_ON =
|
|
46
|
+
process.env.WTR_COVERAGE_SHIM !== '0' &&
|
|
47
|
+
(process.env.WTR_COVERAGE_SHIM === '1' ||
|
|
48
|
+
process.argv.some((a) => a === '--coverage' || a === '--watch-coverage'));
|
|
49
|
+
|
|
50
|
+
if (__WTR_COVERAGE_SHIM_ON) {
|
|
51
|
+
const __origStringify = JSON.stringify;
|
|
52
|
+
const __origParse = JSON.parse;
|
|
53
|
+
// Random sentinel so the literal string in user content can't
|
|
54
|
+
// accidentally trigger the parse shim.
|
|
55
|
+
const __CLONE_SENTINEL =
|
|
56
|
+
'__WTR_COVERAGE_CLONE__' + Math.random().toString(36).slice(2);
|
|
57
|
+
let __pendingCoverages = null;
|
|
58
|
+
|
|
59
|
+
const __looksLikeCoverageArray = (value) => {
|
|
60
|
+
if (!Array.isArray(value) || value.length === 0) return false;
|
|
61
|
+
const first = value[0];
|
|
62
|
+
if (!first || typeof first !== 'object' || Array.isArray(first)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const keys = Object.keys(first);
|
|
66
|
+
if (keys.length === 0) return false;
|
|
67
|
+
if (!keys.every((k) => k.startsWith('/') || /^[a-zA-Z]:[\\/]/.test(k))) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const entry = first[keys[0]];
|
|
71
|
+
return (
|
|
72
|
+
entry &&
|
|
73
|
+
typeof entry === 'object' &&
|
|
74
|
+
(entry.statementMap || entry.fnMap || entry.b || entry.s)
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
JSON.stringify = function (value, ...rest) {
|
|
79
|
+
if (__looksLikeCoverageArray(value)) {
|
|
80
|
+
// A second matched stringify before a matched parse would
|
|
81
|
+
// otherwise silently drop the first payload — clear first.
|
|
82
|
+
__pendingCoverages = value;
|
|
83
|
+
return __CLONE_SENTINEL;
|
|
84
|
+
}
|
|
85
|
+
return __origStringify(value, ...rest);
|
|
86
|
+
};
|
|
87
|
+
JSON.parse = function (text, ...rest) {
|
|
88
|
+
// Always clear pending on parse (matched or not). Keeps the ~512MB
|
|
89
|
+
// reference from outliving a single call pair even if the matched
|
|
90
|
+
// parse never arrives (e.g. wtr throws between the two calls).
|
|
91
|
+
const pending = __pendingCoverages;
|
|
92
|
+
__pendingCoverages = null;
|
|
93
|
+
if (text === __CLONE_SENTINEL && pending) {
|
|
94
|
+
return pending;
|
|
95
|
+
}
|
|
96
|
+
return __origParse(text, ...rest);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
5
99
|
import { esbuildPlugin } from '@web/dev-server-esbuild';
|
|
6
100
|
import fs from 'fs';
|
|
7
101
|
import * as path from 'path';
|
|
@@ -346,6 +440,10 @@ export default {
|
|
|
346
440
|
coverageConfig: {
|
|
347
441
|
include: ['src/**']
|
|
348
442
|
},
|
|
443
|
+
reporters: [
|
|
444
|
+
defaultReporter({ reportTestResults: true, reportTestProgress: true }),
|
|
445
|
+
summaryReporter({ flatten: true })
|
|
446
|
+
],
|
|
349
447
|
testFramework: {
|
|
350
448
|
config: {
|
|
351
449
|
timeout: '10000'
|
|
@@ -380,7 +478,6 @@ export default {
|
|
|
380
478
|
'/api/v2/contacts.json': './static/api/contacts.json',
|
|
381
479
|
'/api/v2/optins.json': './static/api/optins.json',
|
|
382
480
|
'/api/v2/topics.json': './static/api/topics.json',
|
|
383
|
-
'/api/v2/languages.json': './static/api/languages.json',
|
|
384
481
|
'/api/v2/workspace.json': './static/api/workspace.json',
|
|
385
482
|
'/api/internal/locations.json': './static/api/locations.json',
|
|
386
483
|
'/api/internal/orgs.json': './static/api/orgs.json'
|