@nyaruka/temba-components 0.129.7 → 0.129.9
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 +29 -0
- 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 +210 -104
- package/dist/temba-components.js +715 -364
- 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/excellent/helpers.js +2 -2
- package/out-tsc/src/excellent/helpers.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +25 -7
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +11 -1
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +342 -276
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/actions/add_input_labels.js +40 -0
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
- package/out-tsc/src/flow/actions/call_llm.js +56 -3
- package/out-tsc/src/flow/actions/call_llm.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/open_ticket.js +65 -3
- package/out-tsc/src/flow/actions/open_ticket.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/actions/set_run_result.js +75 -0
- package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
- package/out-tsc/src/flow/config.js +4 -0
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +227 -0
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_ticket.js +18 -0
- package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_response.js +27 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/types.js +0 -65
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/form/ArrayEditor.js +87 -57
- 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/FieldRenderer.js +305 -0
- package/out-tsc/src/form/FieldRenderer.js.map +1 -0
- package/out-tsc/src/form/FormField.js +4 -4
- 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 +13 -6
- package/out-tsc/src/form/TextInput.js.map +1 -1
- package/out-tsc/src/form/select/Select.js +52 -24
- package/out-tsc/src/form/select/Select.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +66 -15
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/markdown.js +13 -11
- package/out-tsc/src/markdown.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/ActionHelper.js +2 -0
- package/out-tsc/test/ActionHelper.js.map +1 -1
- package/out-tsc/test/NodeHelper.js +148 -0
- package/out-tsc/test/NodeHelper.js.map +1 -0
- package/out-tsc/test/actions/call_llm.test.js +103 -0
- package/out-tsc/test/actions/call_llm.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_llm_categorize.test.js +532 -0
- package/out-tsc/test/nodes/split_by_llm_categorize.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_random.test.js +150 -0
- package/out-tsc/test/nodes/split_by_random.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js +150 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_response.test.js +171 -0
- package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -0
- package/out-tsc/test/temba-add-input-labels.test.js +70 -0
- package/out-tsc/test/temba-add-input-labels.test.js.map +1 -0
- 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-field-renderer.test.js +296 -0
- package/out-tsc/test/temba-field-renderer.test.js.map +1 -0
- package/out-tsc/test/temba-markdown.test.js +1 -1
- package/out-tsc/test/temba-markdown.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 +471 -0
- package/out-tsc/test/temba-node-editor.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +7 -4
- 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 +5 -1
- 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/call_llm/editor/information-extraction.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
- package/screenshots/truth/actions/call_llm/render/information-extraction.png +0 -0
- package/screenshots/truth/actions/call_llm/render/sentiment-analysis.png +0 -0
- package/screenshots/truth/actions/call_llm/render/summarization.png +0 -0
- package/screenshots/truth/actions/call_llm/render/translation-task.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/remove-from-all-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/router.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/editor/wait.png +0 -0
- package/screenshots/truth/field-renderer/checkbox-checked.png +0 -0
- package/screenshots/truth/field-renderer/checkbox-unchecked.png +0 -0
- package/screenshots/truth/field-renderer/checkbox-with-errors.png +0 -0
- package/screenshots/truth/field-renderer/context-comparison.png +0 -0
- package/screenshots/truth/field-renderer/key-value-with-label.png +0 -0
- package/screenshots/truth/field-renderer/message-editor-with-label.png +0 -0
- package/screenshots/truth/field-renderer/select-multi.png +0 -0
- package/screenshots/truth/field-renderer/select-no-label.png +0 -0
- package/screenshots/truth/field-renderer/select-with-label.png +0 -0
- package/screenshots/truth/field-renderer/text-evaluated.png +0 -0
- package/screenshots/truth/field-renderer/text-no-label.png +0 -0
- package/screenshots/truth/field-renderer/text-with-errors.png +0 -0
- package/screenshots/truth/field-renderer/text-with-label.png +0 -0
- package/screenshots/truth/field-renderer/textarea-evaluated.png +0 -0
- package/screenshots/truth/field-renderer/textarea-with-label.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/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
- package/screenshots/truth/omnibox/selected.png +0 -0
- package/screenshots/truth/select/functions.png +0 -0
- package/screenshots/truth/select/multi-with-endpoint.png +0 -0
- package/screenshots/truth/select/search-enabled.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 +13 -1
- package/src/excellent/helpers.ts +2 -2
- package/src/flow/CanvasNode.ts +22 -1
- package/src/flow/Editor.ts +12 -1
- package/src/flow/NodeEditor.ts +412 -354
- package/src/flow/actions/add_input_labels.ts +45 -0
- package/src/flow/actions/call_llm.ts +57 -3
- package/src/flow/actions/call_webhook.ts +28 -18
- package/src/flow/actions/open_ticket.ts +74 -3
- package/src/flow/actions/send_msg.ts +170 -6
- package/src/flow/actions/set_run_result.ts +83 -0
- package/src/flow/config.ts +4 -0
- package/src/flow/nodes/split_by_llm_categorize.ts +277 -0
- package/src/flow/nodes/split_by_ticket.ts +19 -0
- package/src/flow/nodes/wait_for_response.ts +28 -1
- package/src/flow/types.ts +46 -128
- package/src/form/ArrayEditor.ts +96 -66
- package/src/form/BaseListEditor.ts +22 -6
- package/src/form/FieldRenderer.ts +465 -0
- package/src/form/FormField.ts +4 -4
- 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 +16 -8
- package/src/form/select/Select.ts +55 -24
- package/src/live/ContactChat.ts +69 -19
- package/src/markdown.ts +19 -11
- package/src/store/flow-definition.d.ts +5 -2
- package/static/api/labels.json +31 -0
- package/static/api/topics.json +24 -9
- package/static/api/users.json +35 -16
- package/static/css/temba-components.css +5 -3
- package/static/mr/docs/en-us/editor.json +2588 -0
- package/stress-test.js +143 -0
- package/temba-modules.ts +2 -0
- package/test/ActionHelper.ts +2 -0
- package/test/NodeHelper.ts +184 -0
- package/test/actions/call_llm.test.ts +137 -0
- package/test/nodes/README.md +78 -0
- package/test/nodes/split_by_llm_categorize.test.ts +698 -0
- package/test/nodes/split_by_random.test.ts +177 -0
- package/test/nodes/wait_for_digits.test.ts +176 -0
- package/test/nodes/wait_for_response.test.ts +206 -0
- package/test/temba-add-input-labels.test.ts +87 -0
- package/test/temba-field-config.test.ts +4 -2
- package/test/temba-field-renderer.test.ts +482 -0
- package/test/temba-markdown.test.ts +1 -1
- package/test/temba-message-editor.test.ts +300 -0
- package/test/temba-node-editor.test.ts +590 -0
- package/test/temba-select.test.ts +7 -7
- package/test/temba-textinput.test.ts +26 -0
- package/test/temba-webchat.test.ts +6 -1
- package/test/utils.test.ts +2 -13
- package/test-assets/contacts/history.json +19 -0
- package/test-assets/select/llms.json +18 -0
- package/test-assets/style.css +2 -0
- package/web-dev-mock.mjs +523 -0
- package/web-dev-server.config.mjs +74 -6
- package/web-test-runner.config.mjs +9 -4
- package/test/temba-flow-editor.test.ts.backup +0 -563
- package/test/temba-utils-index.test.ts.backup +0 -1737
|
@@ -5,6 +5,8 @@ import { RapidElement } from '../RapidElement';
|
|
|
5
5
|
import { NODE_CONFIG, ACTION_CONFIG } from './config';
|
|
6
6
|
import { CustomEventType } from '../interfaces';
|
|
7
7
|
import { generateUUID } from '../utils';
|
|
8
|
+
import { FieldRenderer } from '../form/FieldRenderer';
|
|
9
|
+
import { renderMarkdownInline } from '../markdown';
|
|
8
10
|
export class NodeEditor extends RapidElement {
|
|
9
11
|
constructor() {
|
|
10
12
|
super(...arguments);
|
|
@@ -13,6 +15,7 @@ export class NodeEditor extends RapidElement {
|
|
|
13
15
|
this.originalFormData = {};
|
|
14
16
|
this.errors = {};
|
|
15
17
|
this.groupCollapseState = {};
|
|
18
|
+
this.groupHoverState = {};
|
|
16
19
|
}
|
|
17
20
|
static get styles() {
|
|
18
21
|
return css `
|
|
@@ -23,6 +26,10 @@ export class NodeEditor extends RapidElement {
|
|
|
23
26
|
gap: 15px;
|
|
24
27
|
min-width: 400px;
|
|
25
28
|
padding-bottom: 40px;
|
|
29
|
+
|
|
30
|
+
--color-bubble-bg: rgba(var(--primary-rgb), 0.7);
|
|
31
|
+
--color-bubble-border: rgba(0, 0, 0, 0.2);
|
|
32
|
+
--color-bubble-text: #fff;
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
.form-field {
|
|
@@ -31,10 +38,6 @@ export class NodeEditor extends RapidElement {
|
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
.form-field label {
|
|
34
|
-
font-weight: 500;
|
|
35
|
-
margin-bottom: 6px;
|
|
36
|
-
color: #333;
|
|
37
|
-
font-size: 14px;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
.field-errors {
|
|
@@ -95,10 +98,16 @@ export class NodeEditor extends RapidElement {
|
|
|
95
98
|
border-color: var(--color-error, tomato);
|
|
96
99
|
}
|
|
97
100
|
|
|
101
|
+
.form-group.has-bubble {
|
|
102
|
+
border-width: 2px;
|
|
103
|
+
border-color: rgba(var(--primary-rgb), 0.5);
|
|
104
|
+
}
|
|
105
|
+
|
|
98
106
|
.form-group-header {
|
|
99
107
|
background: #f8f9fa;
|
|
100
|
-
padding:
|
|
108
|
+
padding: 8px 10px;
|
|
101
109
|
border-bottom: 1px solid #e0e0e0;
|
|
110
|
+
|
|
102
111
|
display: flex;
|
|
103
112
|
align-items: center;
|
|
104
113
|
justify-content: space-between;
|
|
@@ -106,6 +115,18 @@ export class NodeEditor extends RapidElement {
|
|
|
106
115
|
user-select: none;
|
|
107
116
|
}
|
|
108
117
|
|
|
118
|
+
.form-group.has-bubble .form-group-header {
|
|
119
|
+
background: rgba(var(--primary-rgb), 0.1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.collapsed .form-group-header {
|
|
123
|
+
border: none;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.form-group-header:hover {
|
|
127
|
+
background: rgba(0, 0, 0, 0.05);
|
|
128
|
+
}
|
|
129
|
+
|
|
109
130
|
.form-group-header.collapsible:hover {
|
|
110
131
|
background: #f1f3f4;
|
|
111
132
|
}
|
|
@@ -116,7 +137,7 @@ export class NodeEditor extends RapidElement {
|
|
|
116
137
|
|
|
117
138
|
.form-group-title {
|
|
118
139
|
font-weight: 500;
|
|
119
|
-
color: #
|
|
140
|
+
color: var(--color-label, #777);
|
|
120
141
|
font-size: 14px;
|
|
121
142
|
display: flex;
|
|
122
143
|
}
|
|
@@ -139,13 +160,13 @@ export class NodeEditor extends RapidElement {
|
|
|
139
160
|
}
|
|
140
161
|
|
|
141
162
|
.form-group-content {
|
|
142
|
-
padding:
|
|
163
|
+
padding: 6px;
|
|
143
164
|
display: flex;
|
|
144
165
|
flex-direction: column;
|
|
145
166
|
gap: 15px;
|
|
146
167
|
overflow: hidden;
|
|
147
|
-
transition: all 0.
|
|
148
|
-
|
|
168
|
+
transition: all 0.2s ease-in-out;
|
|
169
|
+
|
|
149
170
|
opacity: 1;
|
|
150
171
|
}
|
|
151
172
|
|
|
@@ -158,9 +179,14 @@ export class NodeEditor extends RapidElement {
|
|
|
158
179
|
|
|
159
180
|
.group-toggle-icon {
|
|
160
181
|
color: #666;
|
|
161
|
-
transition: transform 0.3s ease;
|
|
182
|
+
transition: transform 0.3s ease, opacity 0.3s ease;
|
|
162
183
|
cursor: pointer;
|
|
163
184
|
transform: rotate(0deg);
|
|
185
|
+
opacity: 1;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.group-toggle-icon.faded {
|
|
189
|
+
opacity: 0;
|
|
164
190
|
}
|
|
165
191
|
|
|
166
192
|
.group-toggle-icon.expanded {
|
|
@@ -179,6 +205,58 @@ export class NodeEditor extends RapidElement {
|
|
|
179
205
|
color: var(--color-error, tomato);
|
|
180
206
|
margin-right: 8px;
|
|
181
207
|
}
|
|
208
|
+
|
|
209
|
+
.group-count-bubble {
|
|
210
|
+
border-radius: 50%;
|
|
211
|
+
display: flex;
|
|
212
|
+
align-items: center;
|
|
213
|
+
justify-content: center;
|
|
214
|
+
font-size: 11px;
|
|
215
|
+
font-weight: 600;
|
|
216
|
+
padding: 4px;
|
|
217
|
+
min-width: 12px;
|
|
218
|
+
min-height: 12px;
|
|
219
|
+
position: absolute;
|
|
220
|
+
top: 50%;
|
|
221
|
+
left: 50%;
|
|
222
|
+
transform: translate(-50%, -50%);
|
|
223
|
+
line-height: 0px;
|
|
224
|
+
opacity: 1;
|
|
225
|
+
transition: opacity 0.3s ease;
|
|
226
|
+
background: var(--color-bubble-bg, #fff);
|
|
227
|
+
border: 1px solid var(--color-bubble-border, #777);
|
|
228
|
+
color: var(--color-bubble-text, #000);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.group-count-bubble.hidden {
|
|
232
|
+
opacity: 0;
|
|
233
|
+
pointer-events: none;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.group-checkmark-icon {
|
|
237
|
+
position: absolute;
|
|
238
|
+
top: 50%;
|
|
239
|
+
left: 50%;
|
|
240
|
+
transform: translate(-50%, -50%);
|
|
241
|
+
opacity: 1;
|
|
242
|
+
transition: opacity 0.3s ease;
|
|
243
|
+
border-radius: 50%;
|
|
244
|
+
color: var(--color-bubble-text, #000);
|
|
245
|
+
background: var(--color-bubble-bg, #fff);
|
|
246
|
+
border: 1px solid var(--color-bubble-border, #777);
|
|
247
|
+
padding: 0.2em;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.group-checkmark-icon.hidden {
|
|
251
|
+
opacity: 0;
|
|
252
|
+
pointer-events: none;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.group-toggle-container {
|
|
256
|
+
position: relative;
|
|
257
|
+
display: flex;
|
|
258
|
+
align-items: center;
|
|
259
|
+
}
|
|
182
260
|
`;
|
|
183
261
|
}
|
|
184
262
|
connectedCallback() {
|
|
@@ -187,11 +265,19 @@ export class NodeEditor extends RapidElement {
|
|
|
187
265
|
}
|
|
188
266
|
updated(changedProperties) {
|
|
189
267
|
super.updated(changedProperties);
|
|
190
|
-
if (changedProperties.has('node') ||
|
|
191
|
-
|
|
268
|
+
if (changedProperties.has('node') ||
|
|
269
|
+
changedProperties.has('action') ||
|
|
270
|
+
changedProperties.has('nodeUI')) {
|
|
271
|
+
// For action editing, we only need the action
|
|
272
|
+
if (this.action && (!this.node || !this.nodeUI)) {
|
|
192
273
|
this.openDialog();
|
|
193
274
|
}
|
|
194
|
-
|
|
275
|
+
// For node editing, we need both node and nodeUI
|
|
276
|
+
else if (this.node && this.nodeUI) {
|
|
277
|
+
this.openDialog();
|
|
278
|
+
}
|
|
279
|
+
// If we don't have the required data, close the dialog
|
|
280
|
+
else if (!this.action && (!this.node || !this.nodeUI)) {
|
|
195
281
|
this.isOpen = false;
|
|
196
282
|
}
|
|
197
283
|
}
|
|
@@ -206,9 +292,11 @@ export class NodeEditor extends RapidElement {
|
|
|
206
292
|
this.formData = {};
|
|
207
293
|
this.errors = {};
|
|
208
294
|
this.groupCollapseState = {};
|
|
295
|
+
this.groupHoverState = {};
|
|
209
296
|
}
|
|
210
297
|
initializeFormData() {
|
|
211
|
-
|
|
298
|
+
const nodeConfig = this.getNodeConfig();
|
|
299
|
+
if ((!nodeConfig || nodeConfig.type === 'execute_actions') && this.action) {
|
|
212
300
|
// Action editing mode - use action config
|
|
213
301
|
const actionConfig = ACTION_CONFIG[this.action.type];
|
|
214
302
|
if (actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.toFormData) {
|
|
@@ -216,8 +304,6 @@ export class NodeEditor extends RapidElement {
|
|
|
216
304
|
}
|
|
217
305
|
else {
|
|
218
306
|
this.formData = { ...this.action };
|
|
219
|
-
// Apply smart transformations for select fields that expect {name, value} format
|
|
220
|
-
this.applySmartSelectTransformations(actionConfig);
|
|
221
307
|
}
|
|
222
308
|
// Convert Record objects to array format for key-value editors
|
|
223
309
|
this.processFormDataForEditing();
|
|
@@ -269,45 +355,31 @@ export class NodeEditor extends RapidElement {
|
|
|
269
355
|
});
|
|
270
356
|
this.formData = processed;
|
|
271
357
|
}
|
|
272
|
-
applySmartSelectTransformations(actionConfig) {
|
|
273
|
-
if (!actionConfig)
|
|
274
|
-
return;
|
|
275
|
-
const fields = actionConfig.form;
|
|
276
|
-
if (!fields)
|
|
277
|
-
return;
|
|
278
|
-
Object.entries(fields).forEach(([fieldName, fieldConfig]) => {
|
|
279
|
-
if (this.shouldApplySmartSelectTransformation(fieldName, fieldConfig)) {
|
|
280
|
-
const value = this.formData[fieldName];
|
|
281
|
-
if (Array.isArray(value) &&
|
|
282
|
-
value.length > 0 &&
|
|
283
|
-
typeof value[0] === 'string') {
|
|
284
|
-
// Transform string array to select options format
|
|
285
|
-
this.formData[fieldName] = value.map((item) => ({
|
|
286
|
-
name: item,
|
|
287
|
-
value: item
|
|
288
|
-
}));
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
shouldApplySmartSelectTransformation(fieldName, fieldConfig) {
|
|
294
|
-
var _a;
|
|
295
|
-
const selectConfig = fieldConfig;
|
|
296
|
-
return ((fieldConfig.type === 'select' &&
|
|
297
|
-
(selectConfig.multi || selectConfig.tags) &&
|
|
298
|
-
// Don't transform if already has explicit transformations
|
|
299
|
-
!this.action) ||
|
|
300
|
-
!((_a = ACTION_CONFIG[this.action.type]) === null || _a === void 0 ? void 0 : _a.toFormData));
|
|
301
|
-
}
|
|
302
358
|
isKeyValueField(fieldName) {
|
|
303
359
|
var _a;
|
|
304
360
|
// Check if this field is configured as a key-value type
|
|
361
|
+
const config = this.getConfig();
|
|
362
|
+
const fields = config === null || config === void 0 ? void 0 : config.form;
|
|
363
|
+
return ((_a = fields === null || fields === void 0 ? void 0 : fields[fieldName]) === null || _a === void 0 ? void 0 : _a.type) === 'key-value';
|
|
364
|
+
}
|
|
365
|
+
getConfig() {
|
|
366
|
+
// If we have a node and nodeUI, check if we should use node config
|
|
367
|
+
if (this.node && this.nodeUI) {
|
|
368
|
+
const nodeConfig = this.getNodeConfig();
|
|
369
|
+
// For execute_actions nodes, defer to action editing if an action is selected
|
|
370
|
+
if (this.nodeUI.type === 'execute_actions' && this.action) {
|
|
371
|
+
return ACTION_CONFIG[this.action.type] || null;
|
|
372
|
+
}
|
|
373
|
+
// For all other nodes with a config, use the node config
|
|
374
|
+
if (nodeConfig) {
|
|
375
|
+
return nodeConfig;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// Fall back to action config if no node config or for pure action editing
|
|
305
379
|
if (this.action) {
|
|
306
|
-
|
|
307
|
-
const fields = actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.form;
|
|
308
|
-
return ((_a = fields === null || fields === void 0 ? void 0 : fields[fieldName]) === null || _a === void 0 ? void 0 : _a.type) === 'key-value';
|
|
380
|
+
return ACTION_CONFIG[this.action.type] || null;
|
|
309
381
|
}
|
|
310
|
-
return
|
|
382
|
+
return null;
|
|
311
383
|
}
|
|
312
384
|
getNodeConfig() {
|
|
313
385
|
if (!this.nodeUI)
|
|
@@ -316,17 +388,8 @@ export class NodeEditor extends RapidElement {
|
|
|
316
388
|
return this.nodeUI.type ? NODE_CONFIG[this.nodeUI.type] : null;
|
|
317
389
|
}
|
|
318
390
|
getHeaderColor() {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const actionConfig = ACTION_CONFIG[this.action.type];
|
|
322
|
-
return (actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.color) || '#666666';
|
|
323
|
-
}
|
|
324
|
-
else if (this.node) {
|
|
325
|
-
// Node editing mode
|
|
326
|
-
const nodeConfig = this.getNodeConfig();
|
|
327
|
-
return (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.color) || '#666666';
|
|
328
|
-
}
|
|
329
|
-
return '#666666';
|
|
391
|
+
const config = this.getConfig();
|
|
392
|
+
return (config === null || config === void 0 ? void 0 : config.color) || '#666666';
|
|
330
393
|
}
|
|
331
394
|
handleDialogButtonClick(event) {
|
|
332
395
|
const button = event.detail.button;
|
|
@@ -407,17 +470,16 @@ export class NodeEditor extends RapidElement {
|
|
|
407
470
|
}
|
|
408
471
|
validateForm() {
|
|
409
472
|
const errors = {};
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const actionConfig = ACTION_CONFIG[this.action.type];
|
|
473
|
+
const config = this.getConfig();
|
|
474
|
+
if (config) {
|
|
413
475
|
// Check if new field configuration system is available
|
|
414
|
-
if (
|
|
415
|
-
Object.entries(
|
|
476
|
+
if (config.form) {
|
|
477
|
+
Object.entries(config.form).forEach(([fieldName, fieldConfig]) => {
|
|
416
478
|
const value = this.formData[fieldName];
|
|
417
479
|
// Check required fields
|
|
418
480
|
if (fieldConfig.required &&
|
|
419
481
|
(!value || (Array.isArray(value) && value.length === 0))) {
|
|
420
|
-
errors[fieldName] = `${fieldConfig.label || fieldName} is required
|
|
482
|
+
errors[fieldName] = `${fieldConfig.label || fieldName} is required.`;
|
|
421
483
|
}
|
|
422
484
|
// Check minLength for text fields
|
|
423
485
|
if (typeof value === 'string' &&
|
|
@@ -433,35 +495,26 @@ export class NodeEditor extends RapidElement {
|
|
|
433
495
|
}
|
|
434
496
|
});
|
|
435
497
|
}
|
|
498
|
+
// Universal validation for category arrays to check for reserved names
|
|
499
|
+
this.validateCategoryNames(errors);
|
|
436
500
|
// Run custom validation if available
|
|
437
|
-
if (
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
501
|
+
if (config.validate) {
|
|
502
|
+
if (config.sanitize) {
|
|
503
|
+
config.sanitize(this.formData);
|
|
504
|
+
}
|
|
505
|
+
let customValidation;
|
|
506
|
+
if (this.action) {
|
|
507
|
+
customValidation = config.validate({
|
|
508
|
+
...this.action,
|
|
509
|
+
...this.formData
|
|
510
|
+
});
|
|
442
511
|
}
|
|
443
512
|
else {
|
|
444
|
-
|
|
513
|
+
customValidation = config.validate(this.formData);
|
|
445
514
|
}
|
|
446
|
-
const customValidation = actionConfig.validate(actionForValidation);
|
|
447
515
|
Object.assign(errors, customValidation.errors);
|
|
448
516
|
}
|
|
449
517
|
}
|
|
450
|
-
else if (this.node) {
|
|
451
|
-
// Node validation
|
|
452
|
-
const nodeConfig = this.getNodeConfig();
|
|
453
|
-
// Check required fields from node properties
|
|
454
|
-
if (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.properties) {
|
|
455
|
-
Object.entries(nodeConfig.properties).forEach(([fieldName, fieldConfig]) => {
|
|
456
|
-
const value = this.formData[fieldName];
|
|
457
|
-
// Check required fields
|
|
458
|
-
if (fieldConfig.required &&
|
|
459
|
-
(!value || (Array.isArray(value) && value.length === 0))) {
|
|
460
|
-
errors[fieldName] = `${fieldConfig.label || fieldName} is required`;
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
518
|
// Validate key-value fields for unique keys
|
|
466
519
|
this.validateKeyValueUniqueness(errors);
|
|
467
520
|
return {
|
|
@@ -500,6 +553,34 @@ export class NodeEditor extends RapidElement {
|
|
|
500
553
|
}
|
|
501
554
|
});
|
|
502
555
|
}
|
|
556
|
+
validateCategoryNames(errors) {
|
|
557
|
+
// Universal validation for category names across all node types
|
|
558
|
+
// Prevents use of reserved category names that have special meaning in the system
|
|
559
|
+
// Define reserved category names (case-insensitive)
|
|
560
|
+
const reservedNames = [
|
|
561
|
+
'other',
|
|
562
|
+
'failure',
|
|
563
|
+
'success',
|
|
564
|
+
'all responses',
|
|
565
|
+
'no response'
|
|
566
|
+
];
|
|
567
|
+
// Check all form fields for category arrays
|
|
568
|
+
Object.entries(this.formData).forEach(([fieldName, value]) => {
|
|
569
|
+
if (Array.isArray(value) && fieldName === 'categories') {
|
|
570
|
+
const categories = value.filter((item) => (item === null || item === void 0 ? void 0 : item.name) && item.name.trim() !== '');
|
|
571
|
+
// Check for reserved names
|
|
572
|
+
const reservedUsed = categories
|
|
573
|
+
.filter((item) => {
|
|
574
|
+
const lowerName = item.name.trim().toLowerCase();
|
|
575
|
+
return reservedNames.includes(lowerName);
|
|
576
|
+
})
|
|
577
|
+
.map((item) => item.name.trim()); // Preserve original case
|
|
578
|
+
if (reservedUsed.length > 0) {
|
|
579
|
+
errors[fieldName] = `Reserved category names cannot be used: ${reservedUsed.join(', ')}`;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
503
584
|
formDataToNode(formData = this.formData) {
|
|
504
585
|
var _a;
|
|
505
586
|
if (!this.node)
|
|
@@ -641,29 +722,10 @@ export class NodeEditor extends RapidElement {
|
|
|
641
722
|
return actionConfig.fromFormData(formData);
|
|
642
723
|
}
|
|
643
724
|
else {
|
|
644
|
-
//
|
|
645
|
-
|
|
646
|
-
return { ...this.action, ...processedFormData };
|
|
725
|
+
// Default 1:1 mapping
|
|
726
|
+
return { ...this.action, ...formData };
|
|
647
727
|
}
|
|
648
728
|
}
|
|
649
|
-
reverseSmartSelectTransformations(formData, actionConfig) {
|
|
650
|
-
if (!actionConfig || !actionConfig.form)
|
|
651
|
-
return formData;
|
|
652
|
-
const processed = { ...formData };
|
|
653
|
-
Object.entries(actionConfig.form).forEach(([fieldName, fieldConfig]) => {
|
|
654
|
-
if (this.shouldApplySmartSelectTransformation(fieldName, fieldConfig)) {
|
|
655
|
-
const value = processed[fieldName];
|
|
656
|
-
if (Array.isArray(value) &&
|
|
657
|
-
value.length > 0 &&
|
|
658
|
-
typeof value[0] === 'object' &&
|
|
659
|
-
'value' in value[0]) {
|
|
660
|
-
// Transform select options format back to string array
|
|
661
|
-
processed[fieldName] = value.map((item) => item.value || item.name || item);
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
});
|
|
665
|
-
return processed;
|
|
666
|
-
}
|
|
667
729
|
handleFormFieldChange(propertyName, event) {
|
|
668
730
|
const target = event.target;
|
|
669
731
|
let value;
|
|
@@ -693,13 +755,43 @@ export class NodeEditor extends RapidElement {
|
|
|
693
755
|
}
|
|
694
756
|
// Check for computed values in dependent fields
|
|
695
757
|
this.updateComputedFields(propertyName);
|
|
758
|
+
// Re-evaluate group collapse states that depend on form data
|
|
759
|
+
this.updateGroupCollapseStates();
|
|
696
760
|
// Trigger re-render to handle conditional field visibility
|
|
697
761
|
this.requestUpdate();
|
|
698
762
|
}
|
|
699
|
-
|
|
700
|
-
|
|
763
|
+
updateGroupCollapseStates() {
|
|
764
|
+
const config = this.getConfig();
|
|
765
|
+
if (!(config === null || config === void 0 ? void 0 : config.layout))
|
|
701
766
|
return;
|
|
702
|
-
|
|
767
|
+
this.updateGroupCollapseStatesRecursive(config.layout);
|
|
768
|
+
}
|
|
769
|
+
updateGroupCollapseStatesRecursive(items) {
|
|
770
|
+
items.forEach((item) => {
|
|
771
|
+
if (typeof item === 'object' && item.type === 'group') {
|
|
772
|
+
const { label, collapsed, collapsible } = item;
|
|
773
|
+
// Only update if the group is collapsible and has a function-based collapsed property
|
|
774
|
+
if (collapsible && typeof collapsed === 'function') {
|
|
775
|
+
const newCollapsedState = collapsed(this.formData);
|
|
776
|
+
// Only update if the state has changed to avoid unnecessary re-renders
|
|
777
|
+
if (this.groupCollapseState[label] !== newCollapsedState) {
|
|
778
|
+
this.groupCollapseState = {
|
|
779
|
+
...this.groupCollapseState,
|
|
780
|
+
[label]: newCollapsedState
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
// Recursively check nested items
|
|
785
|
+
this.updateGroupCollapseStatesRecursive(item.items);
|
|
786
|
+
}
|
|
787
|
+
else if (typeof item === 'object' && item.type === 'row') {
|
|
788
|
+
// Recursively check items in rows
|
|
789
|
+
this.updateGroupCollapseStatesRecursive(item.items);
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
updateComputedFields(changedFieldName) {
|
|
794
|
+
const config = this.getConfig();
|
|
703
795
|
if (!(config === null || config === void 0 ? void 0 : config.form))
|
|
704
796
|
return;
|
|
705
797
|
// Check all fields to see if any depend on the changed field
|
|
@@ -746,149 +838,36 @@ export class NodeEditor extends RapidElement {
|
|
|
746
838
|
return fieldContent;
|
|
747
839
|
}
|
|
748
840
|
renderFieldContent(fieldName, config, value, errors) {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
return html `<temba-completion
|
|
769
|
-
name="${fieldName}"
|
|
770
|
-
label="${config.label}"
|
|
771
|
-
?required="${config.required}"
|
|
772
|
-
.errors="${errors}"
|
|
773
|
-
.value="${value || ''}"
|
|
774
|
-
placeholder="${config.placeholder || ''}"
|
|
775
|
-
textarea
|
|
776
|
-
expressions="session"
|
|
777
|
-
style="${minHeightStyle}"
|
|
778
|
-
.helpText="${config.helpText || ''}"
|
|
779
|
-
@input="${(e) => this.handleFormFieldChange(fieldName, e)}"
|
|
780
|
-
></temba-completion>`;
|
|
841
|
+
// Use FieldRenderer for consistent field rendering
|
|
842
|
+
return FieldRenderer.renderField(fieldName, config, value, {
|
|
843
|
+
errors,
|
|
844
|
+
onChange: (e) => {
|
|
845
|
+
// Handle different change event types
|
|
846
|
+
if (fieldName && config.type === 'key-value') {
|
|
847
|
+
// Special handling for key-value editor
|
|
848
|
+
const customEvent = e;
|
|
849
|
+
if (customEvent.detail) {
|
|
850
|
+
this.handleNewFieldChange(fieldName, customEvent.detail.value);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
else if (fieldName && config.type === 'array') {
|
|
854
|
+
// Special handling for array editor
|
|
855
|
+
this.handleNewFieldChange(fieldName, e.target.value);
|
|
856
|
+
}
|
|
857
|
+
else if (fieldName && config.type === 'message-editor') {
|
|
858
|
+
// Special handling for message editor
|
|
859
|
+
this.handleMessageEditorChange(fieldName, e);
|
|
781
860
|
}
|
|
782
861
|
else {
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
label="${config.label}"
|
|
786
|
-
?required="${config.required}"
|
|
787
|
-
.errors="${errors}"
|
|
788
|
-
.value="${value || ''}"
|
|
789
|
-
placeholder="${config.placeholder || ''}"
|
|
790
|
-
textarea
|
|
791
|
-
.rows="${textareaConfig.rows || 3}"
|
|
792
|
-
style="${minHeightStyle}"
|
|
793
|
-
.helpText="${config.helpText || ''}"
|
|
794
|
-
@input="${(e) => this.handleFormFieldChange(fieldName, e)}"
|
|
795
|
-
></temba-textinput>`;
|
|
862
|
+
// Default handling for most field types
|
|
863
|
+
this.handleFormFieldChange(fieldName, e);
|
|
796
864
|
}
|
|
865
|
+
},
|
|
866
|
+
showLabel: true,
|
|
867
|
+
additionalData: {
|
|
868
|
+
attachments: this.formData.attachments || []
|
|
797
869
|
}
|
|
798
|
-
|
|
799
|
-
const selectConfig = config;
|
|
800
|
-
return html `<temba-select
|
|
801
|
-
name="${fieldName}"
|
|
802
|
-
label="${config.label}"
|
|
803
|
-
?required="${config.required}"
|
|
804
|
-
.errors="${errors}"
|
|
805
|
-
.values="${value || (selectConfig.multi ? [] : '')}"
|
|
806
|
-
?multi="${selectConfig.multi}"
|
|
807
|
-
?searchable="${selectConfig.searchable}"
|
|
808
|
-
?tags="${selectConfig.tags}"
|
|
809
|
-
?emails="${selectConfig.emails}"
|
|
810
|
-
placeholder="${selectConfig.placeholder || ''}"
|
|
811
|
-
maxItems="${selectConfig.maxItems || 0}"
|
|
812
|
-
valueKey="${selectConfig.valueKey || 'value'}"
|
|
813
|
-
nameKey="${selectConfig.nameKey || 'name'}"
|
|
814
|
-
endpoint="${selectConfig.endpoint || ''}"
|
|
815
|
-
.helpText="${config.helpText || ''}"
|
|
816
|
-
@change="${(e) => this.handleFormFieldChange(fieldName, e)}"
|
|
817
|
-
>
|
|
818
|
-
${(_a = selectConfig.options) === null || _a === void 0 ? void 0 : _a.map((option) => {
|
|
819
|
-
if (typeof option === 'string') {
|
|
820
|
-
return html `<temba-option
|
|
821
|
-
name="${option}"
|
|
822
|
-
value="${option}"
|
|
823
|
-
></temba-option>`;
|
|
824
|
-
}
|
|
825
|
-
else {
|
|
826
|
-
return html `<temba-option
|
|
827
|
-
name="${option.label || option.name}"
|
|
828
|
-
value="${option.value}"
|
|
829
|
-
></temba-option>`;
|
|
830
|
-
}
|
|
831
|
-
})}
|
|
832
|
-
</temba-select>`;
|
|
833
|
-
}
|
|
834
|
-
case 'key-value':
|
|
835
|
-
return html `<div class="form-field">
|
|
836
|
-
<label>${config.label}${config.required ? ' *' : ''}</label>
|
|
837
|
-
<temba-key-value-editor
|
|
838
|
-
name="${fieldName}"
|
|
839
|
-
.value="${value || []}"
|
|
840
|
-
.sortable="${config.sortable}"
|
|
841
|
-
.keyPlaceholder="${config.keyPlaceholder || 'Key'}"
|
|
842
|
-
.valuePlaceholder="${config.valuePlaceholder || 'Value'}"
|
|
843
|
-
.minRows="${config.minRows || 0}"
|
|
844
|
-
@change="${(e) => {
|
|
845
|
-
if (e.detail) {
|
|
846
|
-
this.handleNewFieldChange(fieldName, e.detail.value);
|
|
847
|
-
}
|
|
848
|
-
}}"
|
|
849
|
-
></temba-key-value-editor>
|
|
850
|
-
${errors.length
|
|
851
|
-
? html `<div class="field-errors">${errors.join(', ')}</div>`
|
|
852
|
-
: ''}
|
|
853
|
-
</div>`;
|
|
854
|
-
case 'array':
|
|
855
|
-
return html `<div class="form-field">
|
|
856
|
-
<label>${config.label}${config.required ? ' *' : ''}</label>
|
|
857
|
-
<temba-array-editor
|
|
858
|
-
.value="${value || []}"
|
|
859
|
-
.itemConfig="${config.itemConfig}"
|
|
860
|
-
.sortable="${config.sortable}"
|
|
861
|
-
.itemLabel="${config.itemLabel || 'Item'}"
|
|
862
|
-
.minItems="${config.minItems || 0}"
|
|
863
|
-
.onItemChange="${config.onItemChange}"
|
|
864
|
-
@change="${(e) => this.handleNewFieldChange(fieldName, e.detail.value)}"
|
|
865
|
-
></temba-array-editor>
|
|
866
|
-
${errors.length
|
|
867
|
-
? html `<div class="field-errors">${errors.join(', ')}</div>`
|
|
868
|
-
: ''}
|
|
869
|
-
</div>`;
|
|
870
|
-
case 'checkbox': {
|
|
871
|
-
const checkboxConfig = config;
|
|
872
|
-
return html `<div class="form-field">
|
|
873
|
-
<temba-checkbox
|
|
874
|
-
name="${fieldName}"
|
|
875
|
-
label="${config.label}"
|
|
876
|
-
.helpText="${config.helpText || ''}"
|
|
877
|
-
?required="${config.required}"
|
|
878
|
-
.errors="${errors}"
|
|
879
|
-
?checked="${value || false}"
|
|
880
|
-
size="${checkboxConfig.size || 1.2}"
|
|
881
|
-
animateChange="${checkboxConfig.animateChange || 'pulse'}"
|
|
882
|
-
@change="${(e) => this.handleFormFieldChange(fieldName, e)}"
|
|
883
|
-
></temba-checkbox>
|
|
884
|
-
${errors.length
|
|
885
|
-
? html `<div class="field-errors">${errors.join(', ')}</div>`
|
|
886
|
-
: ''}
|
|
887
|
-
</div>`;
|
|
888
|
-
}
|
|
889
|
-
default:
|
|
890
|
-
return html `<div>Unsupported field type: ${config.type}</div>`;
|
|
891
|
-
}
|
|
870
|
+
});
|
|
892
871
|
}
|
|
893
872
|
handleGroupToggle(groupLabel) {
|
|
894
873
|
this.groupCollapseState = {
|
|
@@ -896,10 +875,20 @@ export class NodeEditor extends RapidElement {
|
|
|
896
875
|
[groupLabel]: !this.groupCollapseState[groupLabel]
|
|
897
876
|
};
|
|
898
877
|
}
|
|
878
|
+
handleGroupMouseEnter(groupLabel) {
|
|
879
|
+
this.groupHoverState = {
|
|
880
|
+
...this.groupHoverState,
|
|
881
|
+
[groupLabel]: true
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
handleGroupMouseLeave(groupLabel) {
|
|
885
|
+
this.groupHoverState = {
|
|
886
|
+
...this.groupHoverState,
|
|
887
|
+
[groupLabel]: false
|
|
888
|
+
};
|
|
889
|
+
}
|
|
899
890
|
expandGroupsWithErrors(errors) {
|
|
900
|
-
|
|
901
|
-
return;
|
|
902
|
-
const config = ACTION_CONFIG[this.action.type];
|
|
891
|
+
const config = this.getConfig();
|
|
903
892
|
if (!(config === null || config === void 0 ? void 0 : config.layout))
|
|
904
893
|
return;
|
|
905
894
|
const errorFields = new Set(Object.keys(errors));
|
|
@@ -969,25 +958,54 @@ export class NodeEditor extends RapidElement {
|
|
|
969
958
|
`;
|
|
970
959
|
}
|
|
971
960
|
renderGroup(groupConfig, config, renderedFields) {
|
|
972
|
-
var _a;
|
|
973
|
-
const { label, items, collapsible = false, collapsed = false, helpText } = groupConfig;
|
|
961
|
+
var _a, _b;
|
|
962
|
+
const { label, items, collapsible = false, collapsed = false, helpText, getGroupValueCount } = groupConfig;
|
|
974
963
|
// Initialize collapse state if not set
|
|
975
964
|
if (collapsible && !(label in this.groupCollapseState)) {
|
|
965
|
+
// Evaluate collapsed property - can be boolean or function
|
|
966
|
+
const initialCollapsed = typeof collapsed === 'function' ? collapsed(this.formData) : collapsed;
|
|
976
967
|
this.groupCollapseState = {
|
|
977
968
|
...this.groupCollapseState,
|
|
978
|
-
[label]:
|
|
969
|
+
[label]: initialCollapsed
|
|
979
970
|
};
|
|
980
971
|
}
|
|
981
972
|
const isCollapsed = collapsible
|
|
982
|
-
? (_a = this.groupCollapseState[label]) !== null && _a !== void 0 ? _a : collapsed
|
|
973
|
+
? (_a = this.groupCollapseState[label]) !== null && _a !== void 0 ? _a : (typeof collapsed === 'function' ? collapsed(this.formData) : collapsed)
|
|
983
974
|
: false;
|
|
984
975
|
// Check if any field in this group has errors
|
|
985
976
|
const fieldsInGroup = this.collectFieldsFromItems(items);
|
|
986
977
|
const groupHasErrors = fieldsInGroup.some((fieldName) => this.errors[fieldName]);
|
|
978
|
+
// Calculate count for bubble display
|
|
979
|
+
let valueCount = 0;
|
|
980
|
+
let showBubble = false;
|
|
981
|
+
let showCheckmark = false;
|
|
982
|
+
let hasValue = false;
|
|
983
|
+
const isHovered = (_b = this.groupHoverState[label]) !== null && _b !== void 0 ? _b : false;
|
|
984
|
+
if (getGroupValueCount && collapsible) {
|
|
985
|
+
try {
|
|
986
|
+
const result = getGroupValueCount(this.formData);
|
|
987
|
+
if (typeof result === 'boolean') {
|
|
988
|
+
// Boolean result - show checkmark when true
|
|
989
|
+
showCheckmark = result && isCollapsed && !isHovered;
|
|
990
|
+
hasValue = result;
|
|
991
|
+
}
|
|
992
|
+
else if (typeof result === 'number') {
|
|
993
|
+
// Numeric result - show count bubble
|
|
994
|
+
valueCount = result;
|
|
995
|
+
showBubble = valueCount > 0 && isCollapsed && !isHovered;
|
|
996
|
+
hasValue = valueCount > 0;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
catch (error) {
|
|
1000
|
+
console.error(`Error calculating group value count for ${label}:`, error);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
987
1003
|
return html `
|
|
988
1004
|
<div
|
|
989
1005
|
class="form-group ${collapsible ? 'collapsible' : ''} ${groupHasErrors
|
|
990
1006
|
? 'has-errors'
|
|
1007
|
+
: ''} ${isCollapsed ? 'collapsed' : 'expanded'} ${hasValue
|
|
1008
|
+
? 'has-bubble'
|
|
991
1009
|
: ''}"
|
|
992
1010
|
>
|
|
993
1011
|
<div
|
|
@@ -995,11 +1013,19 @@ export class NodeEditor extends RapidElement {
|
|
|
995
1013
|
@click=${collapsible
|
|
996
1014
|
? () => this.handleGroupToggle(label)
|
|
997
1015
|
: undefined}
|
|
1016
|
+
@mouseenter=${collapsible
|
|
1017
|
+
? () => this.handleGroupMouseEnter(label)
|
|
1018
|
+
: undefined}
|
|
1019
|
+
@mouseleave=${collapsible
|
|
1020
|
+
? () => this.handleGroupMouseLeave(label)
|
|
1021
|
+
: undefined}
|
|
998
1022
|
>
|
|
999
1023
|
<div class="form-group-info">
|
|
1000
1024
|
<div class="form-group-title">${label}</div>
|
|
1001
1025
|
${helpText
|
|
1002
|
-
? html `<div class="form-group-help"
|
|
1026
|
+
? html `<div class="form-group-help">
|
|
1027
|
+
${renderMarkdownInline(helpText)}
|
|
1028
|
+
</div>`
|
|
1003
1029
|
: ''}
|
|
1004
1030
|
</div>
|
|
1005
1031
|
${groupHasErrors
|
|
@@ -1010,13 +1036,28 @@ export class NodeEditor extends RapidElement {
|
|
|
1010
1036
|
></temba-icon>`
|
|
1011
1037
|
: ''}
|
|
1012
1038
|
${collapsible && !groupHasErrors
|
|
1013
|
-
? html `<
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1039
|
+
? html `<div class="group-toggle-container">
|
|
1040
|
+
<temba-icon
|
|
1041
|
+
name="arrow_right"
|
|
1042
|
+
size="1.5"
|
|
1043
|
+
class="group-toggle-icon ${isCollapsed
|
|
1017
1044
|
? 'collapsed'
|
|
1018
|
-
: 'expanded'}"
|
|
1019
|
-
|
|
1045
|
+
: 'expanded'} ${showBubble || showCheckmark ? 'faded' : ''}"
|
|
1046
|
+
></temba-icon>
|
|
1047
|
+
${showCheckmark
|
|
1048
|
+
? html `<temba-icon
|
|
1049
|
+
name="check"
|
|
1050
|
+
size="1"
|
|
1051
|
+
class="group-checkmark-icon"
|
|
1052
|
+
></temba-icon>`
|
|
1053
|
+
: showBubble
|
|
1054
|
+
? html `<div
|
|
1055
|
+
class="group-count-bubble ${!showBubble ? 'hidden' : ''}"
|
|
1056
|
+
>
|
|
1057
|
+
${valueCount}
|
|
1058
|
+
</div>`
|
|
1059
|
+
: ''}
|
|
1060
|
+
</div>`
|
|
1020
1061
|
: ''}
|
|
1021
1062
|
</div>
|
|
1022
1063
|
<div
|
|
@@ -1064,16 +1105,33 @@ export class NodeEditor extends RapidElement {
|
|
|
1064
1105
|
delete newErrors[fieldName];
|
|
1065
1106
|
this.errors = newErrors;
|
|
1066
1107
|
}
|
|
1108
|
+
// Re-evaluate group collapse states that depend on form data
|
|
1109
|
+
this.updateGroupCollapseStates();
|
|
1067
1110
|
// Trigger re-render
|
|
1068
1111
|
this.requestUpdate();
|
|
1069
1112
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1113
|
+
handleMessageEditorChange(fieldName, event) {
|
|
1114
|
+
const target = event.target;
|
|
1115
|
+
// Update both text and attachments from the message editor
|
|
1116
|
+
this.formData = {
|
|
1117
|
+
...this.formData,
|
|
1118
|
+
[fieldName]: target.value,
|
|
1119
|
+
attachments: target.attachments || []
|
|
1120
|
+
};
|
|
1121
|
+
// Clear any existing errors for both fields
|
|
1122
|
+
if (this.errors[fieldName]) {
|
|
1123
|
+
const newErrors = { ...this.errors };
|
|
1124
|
+
delete newErrors[fieldName];
|
|
1125
|
+
delete newErrors.attachments;
|
|
1126
|
+
this.errors = newErrors;
|
|
1073
1127
|
}
|
|
1074
|
-
|
|
1128
|
+
// Trigger re-render
|
|
1129
|
+
this.requestUpdate();
|
|
1130
|
+
}
|
|
1131
|
+
renderFields() {
|
|
1132
|
+
const config = this.getConfig();
|
|
1075
1133
|
if (!config) {
|
|
1076
|
-
return html ` <div>No configuration available
|
|
1134
|
+
return html ` <div>No configuration available</div> `;
|
|
1077
1135
|
}
|
|
1078
1136
|
// Use the new fields configuration system
|
|
1079
1137
|
if (config.form) {
|
|
@@ -1099,7 +1157,13 @@ export class NodeEditor extends RapidElement {
|
|
|
1099
1157
|
`;
|
|
1100
1158
|
}
|
|
1101
1159
|
}
|
|
1102
|
-
|
|
1160
|
+
// Fallback for configs without form configuration
|
|
1161
|
+
if (this.action) {
|
|
1162
|
+
return html ` <div>No form configuration available for this action</div> `;
|
|
1163
|
+
}
|
|
1164
|
+
else {
|
|
1165
|
+
return html ` <div>No form configuration available for this node</div> `;
|
|
1166
|
+
}
|
|
1103
1167
|
}
|
|
1104
1168
|
renderActionSection() {
|
|
1105
1169
|
if (!this.node || this.node.actions.length === 0) {
|
|
@@ -1163,11 +1227,10 @@ export class NodeEditor extends RapidElement {
|
|
|
1163
1227
|
return html ``;
|
|
1164
1228
|
}
|
|
1165
1229
|
const headerColor = this.getHeaderColor();
|
|
1166
|
-
const
|
|
1167
|
-
const actionConfig = ACTION_CONFIG[(_a = this.action) === null || _a === void 0 ? void 0 : _a.type];
|
|
1230
|
+
const config = this.getConfig();
|
|
1168
1231
|
return html `
|
|
1169
1232
|
<temba-dialog
|
|
1170
|
-
header="${(
|
|
1233
|
+
header="${(config === null || config === void 0 ? void 0 : config.name) || 'Edit'}"
|
|
1171
1234
|
.open="${this.isOpen}"
|
|
1172
1235
|
@temba-button-clicked=${this.handleDialogButtonClick}
|
|
1173
1236
|
primaryButtonName="Save"
|
|
@@ -1176,7 +1239,7 @@ export class NodeEditor extends RapidElement {
|
|
|
1176
1239
|
>
|
|
1177
1240
|
<div class="node-editor-form">
|
|
1178
1241
|
${this.renderFields()}
|
|
1179
|
-
${((_b =
|
|
1242
|
+
${((_b = (_a = this.getNodeConfig()) === null || _a === void 0 ? void 0 : _a.router) === null || _b === void 0 ? void 0 : _b.configurable)
|
|
1180
1243
|
? this.renderRouterSection()
|
|
1181
1244
|
: null}
|
|
1182
1245
|
</div>
|
|
@@ -1208,4 +1271,7 @@ __decorate([
|
|
|
1208
1271
|
__decorate([
|
|
1209
1272
|
state()
|
|
1210
1273
|
], NodeEditor.prototype, "groupCollapseState", void 0);
|
|
1274
|
+
__decorate([
|
|
1275
|
+
state()
|
|
1276
|
+
], NodeEditor.prototype, "groupHoverState", void 0);
|
|
1211
1277
|
//# sourceMappingURL=NodeEditor.js.map
|