@nyaruka/temba-components 0.129.7 → 0.129.8
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/.devcontainer/Dockerfile +11 -4
- package/.devcontainer/devcontainer.json +3 -2
- package/.github/workflows/build.yml +4 -14
- package/CHANGELOG.md +8 -3
- package/demo/components/flow/example.html +1 -1
- package/demo/components/message-editor/example.html +125 -0
- package/demo/components/textinput/completion.html +1 -0
- package/demo/data/flows/food-order.json +12 -21
- package/demo/data/flows/sample-flow.json +42 -26
- package/dist/temba-components.js +506 -218
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/Thumbnail.js +2 -1
- package/out-tsc/src/display/Thumbnail.js.map +1 -1
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +245 -22
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/actions/call_webhook.js +26 -17
- package/out-tsc/src/flow/actions/call_webhook.js.map +1 -1
- package/out-tsc/src/flow/actions/send_msg.js +147 -6
- package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/form/ArrayEditor.js +111 -38
- package/out-tsc/src/form/ArrayEditor.js.map +1 -1
- package/out-tsc/src/form/BaseListEditor.js +19 -4
- package/out-tsc/src/form/BaseListEditor.js.map +1 -1
- package/out-tsc/src/form/FormField.js +1 -1
- package/out-tsc/src/form/FormField.js.map +1 -1
- package/out-tsc/src/form/KeyValueEditor.js +1 -1
- package/out-tsc/src/form/KeyValueEditor.js.map +1 -1
- package/out-tsc/src/form/MediaPicker.js +13 -1
- package/out-tsc/src/form/MediaPicker.js.map +1 -1
- package/out-tsc/src/form/MessageEditor.js +422 -0
- package/out-tsc/src/form/MessageEditor.js.map +1 -0
- package/out-tsc/src/form/TextInput.js +12 -5
- package/out-tsc/src/form/TextInput.js.map +1 -1
- package/out-tsc/src/form/select/Select.js +4 -4
- package/out-tsc/src/form/select/Select.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +27 -2
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/temba-modules.js +2 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/temba-field-config.test.js +4 -2
- package/out-tsc/test/temba-field-config.test.js.map +1 -1
- package/out-tsc/test/temba-message-editor.test.js +194 -0
- package/out-tsc/test/temba-message-editor.test.js.map +1 -0
- package/out-tsc/test/temba-node-editor.test.js +71 -0
- package/out-tsc/test/temba-node-editor.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +1 -1
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/temba-textinput.test.js +16 -0
- package/out-tsc/test/temba-textinput.test.js.map +1 -1
- package/out-tsc/test/temba-webchat.test.js +4 -0
- package/out-tsc/test/temba-webchat.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +2 -8
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +7 -4
- package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
- package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
- package/screenshots/truth/editor/send_msg.png +0 -0
- package/screenshots/truth/editor/set_contact_language.png +0 -0
- package/screenshots/truth/editor/set_contact_name.png +0 -0
- package/screenshots/truth/editor/set_run_result.png +0 -0
- package/screenshots/truth/formfield/markdown-errors.png +0 -0
- package/screenshots/truth/formfield/no-errors.png +0 -0
- package/screenshots/truth/formfield/plain-text-errors.png +0 -0
- package/screenshots/truth/message-editor/autogrow-initial-content.png +0 -0
- package/screenshots/truth/message-editor/default.png +0 -0
- package/screenshots/truth/message-editor/drag-highlight.png +0 -0
- package/screenshots/truth/message-editor/filtered-attachments.png +0 -0
- package/screenshots/truth/message-editor/with-completion.png +0 -0
- package/screenshots/truth/message-editor/with-properties.png +0 -0
- package/screenshots/truth/textinput/autogrow-initial.png +0 -0
- package/screenshots/truth/textinput/input-form.png +0 -0
- package/src/display/Thumbnail.ts +2 -1
- package/src/events.ts +5 -0
- package/src/flow/NodeEditor.ts +269 -23
- package/src/flow/actions/call_webhook.ts +28 -18
- package/src/flow/actions/send_msg.ts +170 -6
- package/src/flow/types.ts +21 -2
- package/src/form/ArrayEditor.ts +120 -42
- package/src/form/BaseListEditor.ts +22 -6
- package/src/form/FormField.ts +1 -1
- package/src/form/KeyValueEditor.ts +1 -1
- package/src/form/MediaPicker.ts +13 -1
- package/src/form/MessageEditor.ts +449 -0
- package/src/form/TextInput.ts +15 -7
- package/src/form/select/Select.ts +4 -4
- package/src/live/ContactChat.ts +30 -4
- package/static/css/temba-components.css +2 -0
- package/static/mr/docs/en-us/editor.json +2588 -0
- package/stress-test.js +138 -0
- package/temba-modules.ts +2 -0
- package/test/temba-field-config.test.ts +4 -2
- package/test/temba-message-editor.test.ts +300 -0
- package/test/temba-node-editor.test.ts +94 -0
- package/test/temba-select.test.ts +1 -1
- package/test/temba-textinput.test.ts +26 -0
- package/test/temba-webchat.test.ts +5 -0
- package/test/utils.test.ts +2 -13
- package/test-assets/contacts/history.json +19 -0
- package/test-assets/style.css +2 -0
- package/web-dev-mock.mjs +433 -0
- package/web-dev-server.config.mjs +51 -5
- package/web-test-runner.config.mjs +9 -4
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/display/Thumbnail.ts
CHANGED
|
@@ -56,7 +56,8 @@ export class Thumbnail extends RapidElement {
|
|
|
56
56
|
background-repeat: no-repeat;
|
|
57
57
|
border-radius: var(--curvature);
|
|
58
58
|
max-height: calc(var(--thumb-size, 4em) * 2);
|
|
59
|
-
width: var(--thumb-size, 4em);
|
|
59
|
+
max-width: calc(var(--thumb-size, 4em) * 2);
|
|
60
|
+
height: var(--thumb-size, 4em);
|
|
60
61
|
display: flex;
|
|
61
62
|
align-items: center;
|
|
62
63
|
justify-content: center;
|
package/src/events.ts
CHANGED
|
@@ -57,6 +57,11 @@ export interface FlowEvent extends ContactEvent {
|
|
|
57
57
|
status: string;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
export interface RunEvent extends ContactEvent {
|
|
61
|
+
flow: ObjectReference;
|
|
62
|
+
status: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
60
65
|
export interface URNsChangedEvent extends ContactEvent {
|
|
61
66
|
urns: string[];
|
|
62
67
|
}
|
package/src/flow/NodeEditor.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
SelectFieldConfig,
|
|
15
15
|
CheckboxFieldConfig,
|
|
16
16
|
TextareaFieldConfig,
|
|
17
|
+
MessageEditorFieldConfig,
|
|
17
18
|
LayoutItem,
|
|
18
19
|
RowLayoutConfig,
|
|
19
20
|
GroupLayoutConfig
|
|
@@ -31,6 +32,10 @@ export class NodeEditor extends RapidElement {
|
|
|
31
32
|
gap: 15px;
|
|
32
33
|
min-width: 400px;
|
|
33
34
|
padding-bottom: 40px;
|
|
35
|
+
|
|
36
|
+
--color-bubble-bg: rgba(255, 255, 255, 0.8);
|
|
37
|
+
--color-bubble-border: #999;
|
|
38
|
+
--color-bubble-text: #777;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
.form-field {
|
|
@@ -39,10 +44,6 @@ export class NodeEditor extends RapidElement {
|
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
.form-field label {
|
|
42
|
-
font-weight: 500;
|
|
43
|
-
margin-bottom: 6px;
|
|
44
|
-
color: #333;
|
|
45
|
-
font-size: 14px;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
.field-errors {
|
|
@@ -103,10 +104,16 @@ export class NodeEditor extends RapidElement {
|
|
|
103
104
|
border-color: var(--color-error, tomato);
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
.form-group.has-bubble {
|
|
108
|
+
border-width: 1px;
|
|
109
|
+
border-color: var(--color-bubble-border, #aaa);
|
|
110
|
+
}
|
|
111
|
+
|
|
106
112
|
.form-group-header {
|
|
107
113
|
background: #f8f9fa;
|
|
108
|
-
padding:
|
|
114
|
+
padding: 8px 10px;
|
|
109
115
|
border-bottom: 1px solid #e0e0e0;
|
|
116
|
+
|
|
110
117
|
display: flex;
|
|
111
118
|
align-items: center;
|
|
112
119
|
justify-content: space-between;
|
|
@@ -114,6 +121,17 @@ export class NodeEditor extends RapidElement {
|
|
|
114
121
|
user-select: none;
|
|
115
122
|
}
|
|
116
123
|
|
|
124
|
+
.form-group.has-bubble .form-group-header {
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.collapsed .form-group-header {
|
|
128
|
+
border: none;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.form-group-header:hover {
|
|
132
|
+
background: rgba(0, 0, 0, 0.05);
|
|
133
|
+
}
|
|
134
|
+
|
|
117
135
|
.form-group-header.collapsible:hover {
|
|
118
136
|
background: #f1f3f4;
|
|
119
137
|
}
|
|
@@ -124,7 +142,7 @@ export class NodeEditor extends RapidElement {
|
|
|
124
142
|
|
|
125
143
|
.form-group-title {
|
|
126
144
|
font-weight: 500;
|
|
127
|
-
color: #
|
|
145
|
+
color: var(--color-label, #777);
|
|
128
146
|
font-size: 14px;
|
|
129
147
|
display: flex;
|
|
130
148
|
}
|
|
@@ -147,13 +165,13 @@ export class NodeEditor extends RapidElement {
|
|
|
147
165
|
}
|
|
148
166
|
|
|
149
167
|
.form-group-content {
|
|
150
|
-
padding:
|
|
168
|
+
padding: 6px;
|
|
151
169
|
display: flex;
|
|
152
170
|
flex-direction: column;
|
|
153
171
|
gap: 15px;
|
|
154
172
|
overflow: hidden;
|
|
155
|
-
transition: all 0.
|
|
156
|
-
|
|
173
|
+
transition: all 0.2s ease-in-out;
|
|
174
|
+
|
|
157
175
|
opacity: 1;
|
|
158
176
|
}
|
|
159
177
|
|
|
@@ -166,9 +184,14 @@ export class NodeEditor extends RapidElement {
|
|
|
166
184
|
|
|
167
185
|
.group-toggle-icon {
|
|
168
186
|
color: #666;
|
|
169
|
-
transition: transform 0.3s ease;
|
|
187
|
+
transition: transform 0.3s ease, opacity 0.3s ease;
|
|
170
188
|
cursor: pointer;
|
|
171
189
|
transform: rotate(0deg);
|
|
190
|
+
opacity: 1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.group-toggle-icon.faded {
|
|
194
|
+
opacity: 0;
|
|
172
195
|
}
|
|
173
196
|
|
|
174
197
|
.group-toggle-icon.expanded {
|
|
@@ -187,6 +210,58 @@ export class NodeEditor extends RapidElement {
|
|
|
187
210
|
color: var(--color-error, tomato);
|
|
188
211
|
margin-right: 8px;
|
|
189
212
|
}
|
|
213
|
+
|
|
214
|
+
.group-count-bubble {
|
|
215
|
+
border-radius: 50%;
|
|
216
|
+
display: flex;
|
|
217
|
+
align-items: center;
|
|
218
|
+
justify-content: center;
|
|
219
|
+
font-size: 11px;
|
|
220
|
+
font-weight: 600;
|
|
221
|
+
padding: 4px;
|
|
222
|
+
min-width: 12px;
|
|
223
|
+
min-height: 12px;
|
|
224
|
+
position: absolute;
|
|
225
|
+
top: 50%;
|
|
226
|
+
left: 50%;
|
|
227
|
+
transform: translate(-50%, -50%);
|
|
228
|
+
line-height: 0px;
|
|
229
|
+
opacity: 1;
|
|
230
|
+
transition: opacity 0.3s ease;
|
|
231
|
+
background: var(--color-bubble-bg, #fff);
|
|
232
|
+
border: 1px solid var(--color-bubble-border, #777);
|
|
233
|
+
color: var(--color-bubble-text, #000);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.group-count-bubble.hidden {
|
|
237
|
+
opacity: 0;
|
|
238
|
+
pointer-events: none;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.group-checkmark-icon {
|
|
242
|
+
position: absolute;
|
|
243
|
+
top: 50%;
|
|
244
|
+
left: 50%;
|
|
245
|
+
transform: translate(-50%, -50%);
|
|
246
|
+
opacity: 1;
|
|
247
|
+
transition: opacity 0.3s ease;
|
|
248
|
+
border-radius: 50%;
|
|
249
|
+
color: var(--color-bubble-text, #000);
|
|
250
|
+
background: var(--color-bubble-bg, #fff);
|
|
251
|
+
border: 1px solid var(--color-bubble-border, #777);
|
|
252
|
+
padding: 0.2em;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.group-checkmark-icon.hidden {
|
|
256
|
+
opacity: 0;
|
|
257
|
+
pointer-events: none;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.group-toggle-container {
|
|
261
|
+
position: relative;
|
|
262
|
+
display: flex;
|
|
263
|
+
align-items: center;
|
|
264
|
+
}
|
|
190
265
|
`;
|
|
191
266
|
}
|
|
192
267
|
|
|
@@ -214,6 +289,9 @@ export class NodeEditor extends RapidElement {
|
|
|
214
289
|
@state()
|
|
215
290
|
private groupCollapseState: { [key: string]: boolean } = {};
|
|
216
291
|
|
|
292
|
+
@state()
|
|
293
|
+
private groupHoverState: { [key: string]: boolean } = {};
|
|
294
|
+
|
|
217
295
|
connectedCallback(): void {
|
|
218
296
|
super.connectedCallback();
|
|
219
297
|
this.initializeFormData();
|
|
@@ -241,6 +319,7 @@ export class NodeEditor extends RapidElement {
|
|
|
241
319
|
this.formData = {};
|
|
242
320
|
this.errors = {};
|
|
243
321
|
this.groupCollapseState = {};
|
|
322
|
+
this.groupHoverState = {};
|
|
244
323
|
}
|
|
245
324
|
|
|
246
325
|
private initializeFormData(): void {
|
|
@@ -484,7 +563,7 @@ export class NodeEditor extends RapidElement {
|
|
|
484
563
|
) {
|
|
485
564
|
errors[fieldName] = `${
|
|
486
565
|
(fieldConfig as any).label || fieldName
|
|
487
|
-
} is required
|
|
566
|
+
} is required.`;
|
|
488
567
|
}
|
|
489
568
|
|
|
490
569
|
// Check minLength for text fields
|
|
@@ -518,6 +597,11 @@ export class NodeEditor extends RapidElement {
|
|
|
518
597
|
if (actionConfig?.validate) {
|
|
519
598
|
// Convert form data back to action for validation
|
|
520
599
|
let actionForValidation: Action;
|
|
600
|
+
|
|
601
|
+
if (actionConfig.sanitize) {
|
|
602
|
+
actionConfig.sanitize(this.formData);
|
|
603
|
+
}
|
|
604
|
+
|
|
521
605
|
if (actionConfig.fromFormData) {
|
|
522
606
|
actionForValidation = actionConfig.fromFormData(this.formData);
|
|
523
607
|
} else {
|
|
@@ -834,10 +918,49 @@ export class NodeEditor extends RapidElement {
|
|
|
834
918
|
// Check for computed values in dependent fields
|
|
835
919
|
this.updateComputedFields(propertyName);
|
|
836
920
|
|
|
921
|
+
// Re-evaluate group collapse states that depend on form data
|
|
922
|
+
this.updateGroupCollapseStates();
|
|
923
|
+
|
|
837
924
|
// Trigger re-render to handle conditional field visibility
|
|
838
925
|
this.requestUpdate();
|
|
839
926
|
}
|
|
840
927
|
|
|
928
|
+
private updateGroupCollapseStates(): void {
|
|
929
|
+
if (!this.action) return;
|
|
930
|
+
|
|
931
|
+
const config = ACTION_CONFIG[this.action.type];
|
|
932
|
+
if (!config?.layout) return;
|
|
933
|
+
|
|
934
|
+
this.updateGroupCollapseStatesRecursive(config.layout);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
private updateGroupCollapseStatesRecursive(items: LayoutItem[]): void {
|
|
938
|
+
items.forEach((item) => {
|
|
939
|
+
if (typeof item === 'object' && item.type === 'group') {
|
|
940
|
+
const { label, collapsed, collapsible } = item;
|
|
941
|
+
|
|
942
|
+
// Only update if the group is collapsible and has a function-based collapsed property
|
|
943
|
+
if (collapsible && typeof collapsed === 'function') {
|
|
944
|
+
const newCollapsedState = collapsed(this.formData);
|
|
945
|
+
|
|
946
|
+
// Only update if the state has changed to avoid unnecessary re-renders
|
|
947
|
+
if (this.groupCollapseState[label] !== newCollapsedState) {
|
|
948
|
+
this.groupCollapseState = {
|
|
949
|
+
...this.groupCollapseState,
|
|
950
|
+
[label]: newCollapsedState
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// Recursively check nested items
|
|
956
|
+
this.updateGroupCollapseStatesRecursive(item.items);
|
|
957
|
+
} else if (typeof item === 'object' && item.type === 'row') {
|
|
958
|
+
// Recursively check items in rows
|
|
959
|
+
this.updateGroupCollapseStatesRecursive(item.items);
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
|
|
841
964
|
private updateComputedFields(changedFieldName: string): void {
|
|
842
965
|
if (!this.action) return;
|
|
843
966
|
|
|
@@ -980,6 +1103,7 @@ export class NodeEditor extends RapidElement {
|
|
|
980
1103
|
nameKey="${selectConfig.nameKey || 'name'}"
|
|
981
1104
|
endpoint="${selectConfig.endpoint || ''}"
|
|
982
1105
|
.helpText="${config.helpText || ''}"
|
|
1106
|
+
flavor="${selectConfig.flavor || 'small'}"
|
|
983
1107
|
@change="${(e: Event) => this.handleFormFieldChange(fieldName, e)}"
|
|
984
1108
|
>
|
|
985
1109
|
${selectConfig.options?.map((option: any) => {
|
|
@@ -1028,9 +1152,11 @@ export class NodeEditor extends RapidElement {
|
|
|
1028
1152
|
.sortable="${config.sortable}"
|
|
1029
1153
|
.itemLabel="${config.itemLabel || 'Item'}"
|
|
1030
1154
|
.minItems="${config.minItems || 0}"
|
|
1155
|
+
.maxItems="${config.maxItems || 0}"
|
|
1031
1156
|
.onItemChange="${config.onItemChange}"
|
|
1032
|
-
|
|
1033
|
-
|
|
1157
|
+
.isEmptyItemFn="${config.isEmptyItem}"
|
|
1158
|
+
@change="${(e: Event) =>
|
|
1159
|
+
this.handleNewFieldChange(fieldName, (e.target as any).value)}"
|
|
1034
1160
|
></temba-array-editor>
|
|
1035
1161
|
${errors.length
|
|
1036
1162
|
? html`<div class="field-errors">${errors.join(', ')}</div>`
|
|
@@ -1057,6 +1183,30 @@ export class NodeEditor extends RapidElement {
|
|
|
1057
1183
|
</div>`;
|
|
1058
1184
|
}
|
|
1059
1185
|
|
|
1186
|
+
case 'message-editor': {
|
|
1187
|
+
const messageConfig = config as MessageEditorFieldConfig;
|
|
1188
|
+
return html`<temba-message-editor
|
|
1189
|
+
name="${fieldName}"
|
|
1190
|
+
label="${config.label}"
|
|
1191
|
+
?required="${config.required}"
|
|
1192
|
+
.errors="${errors}"
|
|
1193
|
+
.value="${value || ''}"
|
|
1194
|
+
.attachments="${this.formData.attachments || []}"
|
|
1195
|
+
placeholder="${messageConfig.placeholder || ''}"
|
|
1196
|
+
.helpText="${config.helpText || ''}"
|
|
1197
|
+
?autogrow="${messageConfig.autogrow}"
|
|
1198
|
+
?gsm="${messageConfig.gsm}"
|
|
1199
|
+
?disableCompletion="${messageConfig.disableCompletion}"
|
|
1200
|
+
counter="${messageConfig.counter || ''}"
|
|
1201
|
+
accept="${messageConfig.accept || ''}"
|
|
1202
|
+
endpoint="${messageConfig.endpoint || ''}"
|
|
1203
|
+
max-attachments="${messageConfig.maxAttachments || 3}"
|
|
1204
|
+
minHeight="${messageConfig.minHeight || 60}"
|
|
1205
|
+
@change="${(e: Event) =>
|
|
1206
|
+
this.handleMessageEditorChange(fieldName, e)}"
|
|
1207
|
+
></temba-message-editor>`;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1060
1210
|
default:
|
|
1061
1211
|
return html`<div>Unsupported field type: ${(config as any).type}</div>`;
|
|
1062
1212
|
}
|
|
@@ -1069,6 +1219,20 @@ export class NodeEditor extends RapidElement {
|
|
|
1069
1219
|
};
|
|
1070
1220
|
}
|
|
1071
1221
|
|
|
1222
|
+
private handleGroupMouseEnter(groupLabel: string): void {
|
|
1223
|
+
this.groupHoverState = {
|
|
1224
|
+
...this.groupHoverState,
|
|
1225
|
+
[groupLabel]: true
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
private handleGroupMouseLeave(groupLabel: string): void {
|
|
1230
|
+
this.groupHoverState = {
|
|
1231
|
+
...this.groupHoverState,
|
|
1232
|
+
[groupLabel]: false
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1072
1236
|
private expandGroupsWithErrors(errors: { [key: string]: string }): void {
|
|
1073
1237
|
if (!this.action) return;
|
|
1074
1238
|
|
|
@@ -1191,19 +1355,25 @@ export class NodeEditor extends RapidElement {
|
|
|
1191
1355
|
items,
|
|
1192
1356
|
collapsible = false,
|
|
1193
1357
|
collapsed = false,
|
|
1194
|
-
helpText
|
|
1358
|
+
helpText,
|
|
1359
|
+
getGroupValueCount
|
|
1195
1360
|
} = groupConfig;
|
|
1196
1361
|
|
|
1197
1362
|
// Initialize collapse state if not set
|
|
1198
1363
|
if (collapsible && !(label in this.groupCollapseState)) {
|
|
1364
|
+
// Evaluate collapsed property - can be boolean or function
|
|
1365
|
+
const initialCollapsed =
|
|
1366
|
+
typeof collapsed === 'function' ? collapsed(this.formData) : collapsed;
|
|
1367
|
+
|
|
1199
1368
|
this.groupCollapseState = {
|
|
1200
1369
|
...this.groupCollapseState,
|
|
1201
|
-
[label]:
|
|
1370
|
+
[label]: initialCollapsed
|
|
1202
1371
|
};
|
|
1203
1372
|
}
|
|
1204
1373
|
|
|
1205
1374
|
const isCollapsed = collapsible
|
|
1206
|
-
? this.groupCollapseState[label] ??
|
|
1375
|
+
? this.groupCollapseState[label] ??
|
|
1376
|
+
(typeof collapsed === 'function' ? collapsed(this.formData) : collapsed)
|
|
1207
1377
|
: false;
|
|
1208
1378
|
|
|
1209
1379
|
// Check if any field in this group has errors
|
|
@@ -1212,10 +1382,41 @@ export class NodeEditor extends RapidElement {
|
|
|
1212
1382
|
(fieldName) => this.errors[fieldName]
|
|
1213
1383
|
);
|
|
1214
1384
|
|
|
1385
|
+
// Calculate count for bubble display
|
|
1386
|
+
let valueCount = 0;
|
|
1387
|
+
let showBubble = false;
|
|
1388
|
+
let showCheckmark = false;
|
|
1389
|
+
let hasValue = false;
|
|
1390
|
+
const isHovered = this.groupHoverState[label] ?? false;
|
|
1391
|
+
|
|
1392
|
+
if (getGroupValueCount && collapsible) {
|
|
1393
|
+
try {
|
|
1394
|
+
const result = getGroupValueCount(this.formData);
|
|
1395
|
+
|
|
1396
|
+
if (typeof result === 'boolean') {
|
|
1397
|
+
// Boolean result - show checkmark when true
|
|
1398
|
+
showCheckmark = result && isCollapsed && !isHovered;
|
|
1399
|
+
hasValue = result;
|
|
1400
|
+
} else if (typeof result === 'number') {
|
|
1401
|
+
// Numeric result - show count bubble
|
|
1402
|
+
valueCount = result;
|
|
1403
|
+
showBubble = valueCount > 0 && isCollapsed && !isHovered;
|
|
1404
|
+
hasValue = valueCount > 0;
|
|
1405
|
+
}
|
|
1406
|
+
} catch (error) {
|
|
1407
|
+
console.error(
|
|
1408
|
+
`Error calculating group value count for ${label}:`,
|
|
1409
|
+
error
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1215
1414
|
return html`
|
|
1216
1415
|
<div
|
|
1217
1416
|
class="form-group ${collapsible ? 'collapsible' : ''} ${groupHasErrors
|
|
1218
1417
|
? 'has-errors'
|
|
1418
|
+
: ''} ${isCollapsed ? 'collapsed' : 'expanded'} ${hasValue
|
|
1419
|
+
? 'has-bubble'
|
|
1219
1420
|
: ''}"
|
|
1220
1421
|
>
|
|
1221
1422
|
<div
|
|
@@ -1223,6 +1424,12 @@ export class NodeEditor extends RapidElement {
|
|
|
1223
1424
|
@click=${collapsible
|
|
1224
1425
|
? () => this.handleGroupToggle(label)
|
|
1225
1426
|
: undefined}
|
|
1427
|
+
@mouseenter=${collapsible
|
|
1428
|
+
? () => this.handleGroupMouseEnter(label)
|
|
1429
|
+
: undefined}
|
|
1430
|
+
@mouseleave=${collapsible
|
|
1431
|
+
? () => this.handleGroupMouseLeave(label)
|
|
1432
|
+
: undefined}
|
|
1226
1433
|
>
|
|
1227
1434
|
<div class="form-group-info">
|
|
1228
1435
|
<div class="form-group-title">${label}</div>
|
|
@@ -1238,13 +1445,28 @@ export class NodeEditor extends RapidElement {
|
|
|
1238
1445
|
></temba-icon>`
|
|
1239
1446
|
: ''}
|
|
1240
1447
|
${collapsible && !groupHasErrors
|
|
1241
|
-
? html`<
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1448
|
+
? html`<div class="group-toggle-container">
|
|
1449
|
+
<temba-icon
|
|
1450
|
+
name="arrow_right"
|
|
1451
|
+
size="1.5"
|
|
1452
|
+
class="group-toggle-icon ${isCollapsed
|
|
1453
|
+
? 'collapsed'
|
|
1454
|
+
: 'expanded'} ${showBubble || showCheckmark ? 'faded' : ''}"
|
|
1455
|
+
></temba-icon>
|
|
1456
|
+
${showCheckmark
|
|
1457
|
+
? html`<temba-icon
|
|
1458
|
+
name="check"
|
|
1459
|
+
size="1"
|
|
1460
|
+
class="group-checkmark-icon"
|
|
1461
|
+
></temba-icon>`
|
|
1462
|
+
: showBubble
|
|
1463
|
+
? html`<div
|
|
1464
|
+
class="group-count-bubble ${!showBubble ? 'hidden' : ''}"
|
|
1465
|
+
>
|
|
1466
|
+
${valueCount}
|
|
1467
|
+
</div>`
|
|
1468
|
+
: ''}
|
|
1469
|
+
</div>`
|
|
1248
1470
|
: ''}
|
|
1249
1471
|
</div>
|
|
1250
1472
|
<div
|
|
@@ -1305,6 +1527,30 @@ export class NodeEditor extends RapidElement {
|
|
|
1305
1527
|
this.errors = newErrors;
|
|
1306
1528
|
}
|
|
1307
1529
|
|
|
1530
|
+
// Re-evaluate group collapse states that depend on form data
|
|
1531
|
+
this.updateGroupCollapseStates();
|
|
1532
|
+
|
|
1533
|
+
// Trigger re-render
|
|
1534
|
+
this.requestUpdate();
|
|
1535
|
+
}
|
|
1536
|
+
private handleMessageEditorChange(fieldName: string, event: Event): void {
|
|
1537
|
+
const target = event.target as any;
|
|
1538
|
+
|
|
1539
|
+
// Update both text and attachments from the message editor
|
|
1540
|
+
this.formData = {
|
|
1541
|
+
...this.formData,
|
|
1542
|
+
[fieldName]: target.value,
|
|
1543
|
+
attachments: target.attachments || []
|
|
1544
|
+
};
|
|
1545
|
+
|
|
1546
|
+
// Clear any existing errors for both fields
|
|
1547
|
+
if (this.errors[fieldName]) {
|
|
1548
|
+
const newErrors = { ...this.errors };
|
|
1549
|
+
delete newErrors[fieldName];
|
|
1550
|
+
delete newErrors.attachments;
|
|
1551
|
+
this.errors = newErrors;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1308
1554
|
// Trigger re-render
|
|
1309
1555
|
this.requestUpdate();
|
|
1310
1556
|
}
|
|
@@ -2,6 +2,19 @@ import { html } from 'lit-html';
|
|
|
2
2
|
import { ActionConfig, COLORS } from '../types';
|
|
3
3
|
import { Node, CallWebhook } from '../../store/flow-definition';
|
|
4
4
|
|
|
5
|
+
const defaultPost = `@(json(object(
|
|
6
|
+
"contact", object(
|
|
7
|
+
"uuid", contact.uuid,
|
|
8
|
+
"name", contact.name,
|
|
9
|
+
"urn", contact.urn
|
|
10
|
+
),
|
|
11
|
+
"flow", object(
|
|
12
|
+
"uuid", run.flow.uuid,
|
|
13
|
+
"name", run.flow.name
|
|
14
|
+
),
|
|
15
|
+
"results", foreach_value(results, extract_object, "value", "category")
|
|
16
|
+
)))`;
|
|
17
|
+
|
|
5
18
|
export const call_webhook: ActionConfig = {
|
|
6
19
|
name: 'Call Webhook',
|
|
7
20
|
color: COLORS.call,
|
|
@@ -19,7 +32,7 @@ export const call_webhook: ActionConfig = {
|
|
|
19
32
|
required: true,
|
|
20
33
|
options: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH'],
|
|
21
34
|
maxWidth: '120px',
|
|
22
|
-
searchable:
|
|
35
|
+
searchable: false
|
|
23
36
|
},
|
|
24
37
|
url: {
|
|
25
38
|
type: 'text',
|
|
@@ -51,23 +64,10 @@ export const call_webhook: ActionConfig = {
|
|
|
51
64
|
? values.method[0].value || values.method[0].name
|
|
52
65
|
: values.method;
|
|
53
66
|
|
|
54
|
-
const defaultTemplate = `@(json(object(
|
|
55
|
-
"contact", object(
|
|
56
|
-
"uuid", contact.uuid,
|
|
57
|
-
"name", contact.name,
|
|
58
|
-
"urn", contact.urn
|
|
59
|
-
),
|
|
60
|
-
"flow", object(
|
|
61
|
-
"uuid", run.flow.uuid,
|
|
62
|
-
"name", run.flow.name
|
|
63
|
-
),
|
|
64
|
-
"results", foreach_value(results, extract_object, "value", "category")
|
|
65
|
-
)))`;
|
|
66
|
-
|
|
67
67
|
if (method === 'POST') {
|
|
68
68
|
// For POST, provide the template if body is empty or was never set by user
|
|
69
69
|
if (!currentValue || currentValue.trim() === '') {
|
|
70
|
-
return
|
|
70
|
+
return defaultPost;
|
|
71
71
|
}
|
|
72
72
|
} else {
|
|
73
73
|
// For non-POST methods, clear the body if it was auto-generated or empty
|
|
@@ -80,7 +80,7 @@ export const call_webhook: ActionConfig = {
|
|
|
80
80
|
isOriginallyEmpty ||
|
|
81
81
|
!currentValue ||
|
|
82
82
|
currentValue.trim() === '' ||
|
|
83
|
-
currentValue.trim() ===
|
|
83
|
+
currentValue.trim() === defaultPost.trim()
|
|
84
84
|
) {
|
|
85
85
|
return '';
|
|
86
86
|
}
|
|
@@ -100,7 +100,10 @@ export const call_webhook: ActionConfig = {
|
|
|
100
100
|
items: ['headers'],
|
|
101
101
|
collapsible: true,
|
|
102
102
|
collapsed: true,
|
|
103
|
-
helpText: 'Configure authentication or custom headers'
|
|
103
|
+
helpText: 'Configure authentication or custom headers',
|
|
104
|
+
getGroupValueCount: (formData: any) => {
|
|
105
|
+
return formData.headers?.length + 10 || 0;
|
|
106
|
+
}
|
|
104
107
|
},
|
|
105
108
|
{
|
|
106
109
|
type: 'group',
|
|
@@ -108,7 +111,14 @@ export const call_webhook: ActionConfig = {
|
|
|
108
111
|
items: ['body'],
|
|
109
112
|
collapsible: true,
|
|
110
113
|
collapsed: true,
|
|
111
|
-
helpText: 'Configure the request payload'
|
|
114
|
+
helpText: 'Configure the request payload',
|
|
115
|
+
getGroupValueCount: (formData: any) => {
|
|
116
|
+
return !!(
|
|
117
|
+
formData.body &&
|
|
118
|
+
formData.body.trim() !== '' &&
|
|
119
|
+
formData.body !== defaultPost
|
|
120
|
+
);
|
|
121
|
+
}
|
|
112
122
|
}
|
|
113
123
|
],
|
|
114
124
|
toFormData: (action: CallWebhook) => {
|