@nyaruka/temba-components 0.129.3 → 0.129.5
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/.eslintrc.js +1 -0
- package/.github/workflows/build.yml +135 -3
- package/CHANGELOG.md +19 -0
- package/demo/data/flows/sample-flow.json +110 -87
- package/demo/field-config-demo.html +135 -0
- package/dist/temba-components.js +1257 -675
- package/dist/temba-components.js.map +1 -1
- package/docs/ActionEditor-Migration.md +118 -0
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/flow/{EditorNode.js → CanvasNode.js} +345 -42
- package/out-tsc/src/flow/CanvasNode.js.map +1 -0
- package/out-tsc/src/flow/Editor.js +107 -3
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +1211 -0
- package/out-tsc/src/flow/NodeEditor.js.map +1 -0
- package/out-tsc/src/flow/Plumber.js +0 -6
- package/out-tsc/src/flow/Plumber.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_groups.js +40 -0
- package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -0
- package/out-tsc/src/flow/actions/add_contact_urn.js +16 -0
- package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -0
- package/out-tsc/src/flow/actions/add_input_labels.js +11 -0
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -0
- package/out-tsc/src/flow/actions/call_classifier.js +11 -0
- package/out-tsc/src/flow/actions/call_classifier.js.map +1 -0
- package/out-tsc/src/flow/actions/call_llm.js +11 -0
- package/out-tsc/src/flow/actions/call_llm.js.map +1 -0
- package/out-tsc/src/flow/actions/call_resthook.js +11 -0
- package/out-tsc/src/flow/actions/call_resthook.js.map +1 -0
- package/out-tsc/src/flow/actions/call_webhook.js +122 -0
- package/out-tsc/src/flow/actions/call_webhook.js.map +1 -0
- package/out-tsc/src/flow/actions/enter_flow.js +14 -0
- package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
- package/out-tsc/src/flow/actions/open_ticket.js +11 -0
- package/out-tsc/src/flow/actions/open_ticket.js.map +1 -0
- package/out-tsc/src/flow/actions/play_audio.js +11 -0
- package/out-tsc/src/flow/actions/play_audio.js.map +1 -0
- package/out-tsc/src/flow/actions/remove_contact_groups.js +62 -0
- package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -0
- package/out-tsc/src/flow/actions/request_optin.js +11 -0
- package/out-tsc/src/flow/actions/request_optin.js.map +1 -0
- package/out-tsc/src/flow/actions/say_msg.js +11 -0
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -0
- package/out-tsc/src/flow/actions/send_broadcast.js +33 -0
- package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -0
- package/out-tsc/src/flow/actions/send_email.js +56 -0
- package/out-tsc/src/flow/actions/send_email.js.map +1 -0
- package/out-tsc/src/flow/actions/send_msg.js +55 -0
- package/out-tsc/src/flow/actions/send_msg.js.map +1 -0
- package/out-tsc/src/flow/actions/set_contact_channel.js +12 -0
- package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -0
- package/out-tsc/src/flow/actions/set_contact_field.js +12 -0
- package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -0
- package/out-tsc/src/flow/actions/set_contact_language.js +10 -0
- package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -0
- package/out-tsc/src/flow/actions/set_contact_name.js +10 -0
- package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -0
- package/out-tsc/src/flow/actions/set_contact_status.js +10 -0
- package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -0
- package/out-tsc/src/flow/actions/set_run_result.js +10 -0
- package/out-tsc/src/flow/actions/set_run_result.js.map +1 -0
- package/out-tsc/src/flow/actions/split_by_expression_example.js +77 -0
- package/out-tsc/src/flow/actions/split_by_expression_example.js.map +1 -0
- package/out-tsc/src/flow/actions/start_session.js +11 -0
- package/out-tsc/src/flow/actions/start_session.js.map +1 -0
- package/out-tsc/src/flow/actions/transfer_airtime.js +11 -0
- package/out-tsc/src/flow/actions/transfer_airtime.js.map +1 -0
- package/out-tsc/src/flow/config.js +88 -193
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/nodes/execute_actions.js +4 -0
- package/out-tsc/src/flow/nodes/execute_actions.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_airtime.js +9 -0
- package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_contact_field.js +7 -0
- package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_expression.js +7 -0
- package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_groups.js +7 -0
- package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_random.js +10 -0
- package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_run_result.js +7 -0
- package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_scheme.js +7 -0
- package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_subflow.js +9 -0
- package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_webhook.js +18 -0
- package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_audio.js +7 -0
- package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_digits.js +7 -0
- package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_image.js +7 -0
- package/out-tsc/src/flow/nodes/wait_for_image.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_location.js +7 -0
- package/out-tsc/src/flow/nodes/wait_for_location.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_menu.js +7 -0
- package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_response.js +7 -0
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_video.js +7 -0
- package/out-tsc/src/flow/nodes/wait_for_video.js.map +1 -0
- package/out-tsc/src/flow/types.js +79 -0
- package/out-tsc/src/flow/types.js.map +1 -0
- package/out-tsc/src/flow/utils.js +65 -0
- package/out-tsc/src/flow/utils.js.map +1 -0
- package/out-tsc/src/form/ArrayEditor.js +199 -0
- package/out-tsc/src/form/ArrayEditor.js.map +1 -0
- package/out-tsc/src/form/BaseListEditor.js +128 -0
- package/out-tsc/src/form/BaseListEditor.js.map +1 -0
- package/out-tsc/src/form/Checkbox.js +17 -2
- package/out-tsc/src/form/Checkbox.js.map +1 -1
- package/out-tsc/src/form/Completion.js +6 -0
- package/out-tsc/src/form/Completion.js.map +1 -1
- package/out-tsc/src/form/FormField.js +110 -11
- package/out-tsc/src/form/FormField.js.map +1 -1
- package/out-tsc/src/form/KeyValueEditor.js +223 -0
- package/out-tsc/src/form/KeyValueEditor.js.map +1 -0
- package/out-tsc/src/form/select/Select.js +92 -32
- package/out-tsc/src/form/select/Select.js.map +1 -1
- package/out-tsc/src/interfaces.js +6 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +2 -76
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/temba-modules.js +9 -2
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/ActionHelper.js +116 -0
- package/out-tsc/test/ActionHelper.js.map +1 -0
- package/out-tsc/test/actions/add_contact_groups.test.js +66 -0
- package/out-tsc/test/actions/add_contact_groups.test.js.map +1 -0
- package/out-tsc/test/actions/remove_contact_groups.test.js +226 -0
- package/out-tsc/test/actions/remove_contact_groups.test.js.map +1 -0
- package/out-tsc/test/actions/send_email.test.js +160 -0
- package/out-tsc/test/actions/send_email.test.js.map +1 -0
- package/out-tsc/test/actions/send_msg.test.js +95 -0
- package/out-tsc/test/actions/send_msg.test.js.map +1 -0
- package/out-tsc/test/temba-action-editing-integration.test.js +183 -0
- package/out-tsc/test/temba-action-editing-integration.test.js.map +1 -0
- package/out-tsc/test/temba-checkbox.test.js +1 -1
- package/out-tsc/test/temba-checkbox.test.js.map +1 -1
- package/out-tsc/test/temba-field-config.test.js +133 -0
- package/out-tsc/test/temba-field-config.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor-node.test.js +14 -14
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-node-editor.test.js +283 -0
- package/out-tsc/test/temba-node-editor.test.js.map +1 -0
- package/out-tsc/test/temba-select.test.js +158 -0
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/package.json +1 -1
- 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/add_contact_groups/render/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/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/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/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_email/render/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/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/actions/send_msg/render/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/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/formfield/markdown-errors.png +0 -0
- package/screenshots/truth/formfield/plain-text-errors.png +0 -0
- package/screenshots/truth/formfield/widget-only-markdown-errors.png +0 -0
- package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
- package/src/events.ts +1 -40
- package/src/flow/{EditorNode.ts → CanvasNode.ts} +424 -48
- package/src/flow/Editor.ts +140 -4
- package/src/flow/NodeEditor.ts +1454 -0
- package/src/flow/Plumber.ts +0 -9
- package/src/flow/actions/add_contact_groups.ts +42 -0
- package/src/flow/actions/add_contact_urn.ts +17 -0
- package/src/flow/actions/add_input_labels.ts +12 -0
- package/src/flow/actions/call_classifier.ts +12 -0
- package/src/flow/actions/call_llm.ts +12 -0
- package/src/flow/actions/call_resthook.ts +12 -0
- package/src/flow/actions/call_webhook.ts +133 -0
- package/src/flow/actions/enter_flow.ts +15 -0
- package/src/flow/actions/open_ticket.ts +12 -0
- package/src/flow/actions/play_audio.ts +12 -0
- package/src/flow/actions/remove_contact_groups.ts +66 -0
- package/src/flow/actions/request_optin.ts +12 -0
- package/src/flow/actions/say_msg.ts +12 -0
- package/src/flow/actions/send_broadcast.ts +35 -0
- package/src/flow/actions/send_email.ts +60 -0
- package/src/flow/actions/send_msg.ts +58 -0
- package/src/flow/actions/set_contact_channel.ts +13 -0
- package/src/flow/actions/set_contact_field.ts +13 -0
- package/src/flow/actions/set_contact_language.ts +11 -0
- package/src/flow/actions/set_contact_name.ts +11 -0
- package/src/flow/actions/set_contact_status.ts +11 -0
- package/src/flow/actions/set_run_result.ts +11 -0
- package/src/flow/actions/split_by_expression_example.ts +88 -0
- package/src/flow/actions/start_session.ts +12 -0
- package/src/flow/actions/transfer_airtime.ts +12 -0
- package/src/flow/config.ts +93 -232
- package/src/flow/nodes/execute_actions.ts +5 -0
- package/src/flow/nodes/split_by_airtime.ts +9 -0
- package/src/flow/nodes/split_by_contact_field.ts +7 -0
- package/src/flow/nodes/split_by_expression.ts +7 -0
- package/src/flow/nodes/split_by_groups.ts +7 -0
- package/src/flow/nodes/split_by_random.ts +10 -0
- package/src/flow/nodes/split_by_run_result.ts +7 -0
- package/src/flow/nodes/split_by_scheme.ts +7 -0
- package/src/flow/nodes/split_by_subflow.ts +9 -0
- package/src/flow/nodes/split_by_webhook.ts +19 -0
- package/src/flow/nodes/wait_for_audio.ts +7 -0
- package/src/flow/nodes/wait_for_digits.ts +7 -0
- package/src/flow/nodes/wait_for_image.ts +7 -0
- package/src/flow/nodes/wait_for_location.ts +7 -0
- package/src/flow/nodes/wait_for_menu.ts +7 -0
- package/src/flow/nodes/wait_for_response.ts +7 -0
- package/src/flow/nodes/wait_for_video.ts +7 -0
- package/src/flow/types.ts +352 -0
- package/src/flow/utils.ts +76 -0
- package/src/form/ArrayEditor.ts +240 -0
- package/src/form/BaseListEditor.ts +177 -0
- package/src/form/Checkbox.ts +22 -3
- package/src/form/Completion.ts +6 -0
- package/src/form/FormField.ts +115 -11
- package/src/form/KeyValueEditor.ts +251 -0
- package/src/form/select/Select.ts +105 -32
- package/src/interfaces.ts +7 -2
- package/src/live/ContactChat.ts +3 -97
- package/src/store/flow-definition.d.ts +6 -1
- package/static/api/contacts.json +30 -0
- package/static/api/groups.json +4 -426
- package/static/api/locations.json +24 -0
- package/static/api/media.json +5 -0
- package/static/api/optins.json +16 -0
- package/static/api/orgs.json +13 -0
- package/static/api/topics.json +21 -0
- package/static/api/users.json +26 -0
- package/static/css/temba-components.css +3 -6
- package/temba-modules.ts +9 -2
- package/test/ActionHelper.ts +142 -0
- package/test/actions/add_contact_groups.test.ts +89 -0
- package/test/actions/remove_contact_groups.test.ts +265 -0
- package/test/actions/send_email.test.ts +214 -0
- package/test/actions/send_msg.test.ts +130 -0
- package/test/temba-action-editing-integration.test.ts +240 -0
- package/test/temba-checkbox.test.ts +1 -1
- package/test/temba-field-config.test.ts +152 -0
- package/test/temba-flow-editor-node.test.ts +18 -18
- package/test/temba-node-editor.test.ts +353 -0
- package/test/temba-select.test.ts +234 -0
- package/test-assets/contacts/history.json +11 -33
- package/web-dev-server.config.mjs +34 -0
- package/.github/workflows/coverage.yml +0 -80
- package/demo/sticky-note-demo.html +0 -155
- package/out-tsc/src/flow/EditorNode.js.map +0 -1
- package/out-tsc/src/flow/render.js +0 -358
- package/out-tsc/src/flow/render.js.map +0 -1
- package/out-tsc/test/temba-flow-render.test.js +0 -794
- package/out-tsc/test/temba-flow-render.test.js.map +0 -1
- package/src/flow/render.ts +0 -443
- package/test/temba-flow-render.test.ts +0 -1003
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { TemplateResult } from 'lit-html';
|
|
2
|
+
import { Action } from '../store/flow-definition';
|
|
3
|
+
|
|
4
|
+
export interface ValidationResult {
|
|
5
|
+
valid: boolean;
|
|
6
|
+
errors: { [key: string]: string };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Component attribute interfaces - these define what's allowed for each component type
|
|
10
|
+
export interface TextInputAttributes {
|
|
11
|
+
type?: 'text' | 'email' | 'number' | 'url' | 'tel';
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
clearable?: boolean;
|
|
14
|
+
maxlength?: number;
|
|
15
|
+
gsm?: boolean;
|
|
16
|
+
autogrow?: boolean;
|
|
17
|
+
textarea?: boolean;
|
|
18
|
+
submitOnEnter?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CompletionAttributes {
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
clearable?: boolean;
|
|
24
|
+
maxlength?: number;
|
|
25
|
+
gsm?: boolean;
|
|
26
|
+
autogrow?: boolean;
|
|
27
|
+
textarea?: boolean;
|
|
28
|
+
expressions?: string;
|
|
29
|
+
counter?: string;
|
|
30
|
+
minHeight?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SelectAttributes {
|
|
34
|
+
placeholder?: string;
|
|
35
|
+
multi?: boolean;
|
|
36
|
+
searchable?: boolean;
|
|
37
|
+
tags?: boolean;
|
|
38
|
+
emails?: boolean;
|
|
39
|
+
clearable?: boolean;
|
|
40
|
+
endpoint?: string;
|
|
41
|
+
valueKey?: string;
|
|
42
|
+
nameKey?: string;
|
|
43
|
+
queryParam?: string;
|
|
44
|
+
maxItems?: number;
|
|
45
|
+
maxItemsText?: string;
|
|
46
|
+
expressions?: string;
|
|
47
|
+
options?: Array<{ name: string; value: any }>;
|
|
48
|
+
sorted?: boolean;
|
|
49
|
+
allowCreate?: boolean;
|
|
50
|
+
jsonValue?: boolean;
|
|
51
|
+
spaceSelect?: boolean;
|
|
52
|
+
infoText?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface CheckboxAttributes {
|
|
56
|
+
label?: string;
|
|
57
|
+
size?: number;
|
|
58
|
+
disabled?: boolean;
|
|
59
|
+
animateChange?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface SliderAttributes {
|
|
63
|
+
min?: number;
|
|
64
|
+
max?: number;
|
|
65
|
+
range?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Widget configuration using discriminated union for type safety
|
|
69
|
+
export type WidgetConfig =
|
|
70
|
+
| { type: 'temba-textinput'; attributes?: TextInputAttributes }
|
|
71
|
+
| { type: 'temba-completion'; attributes?: CompletionAttributes }
|
|
72
|
+
| { type: 'temba-select'; attributes?: SelectAttributes }
|
|
73
|
+
| { type: 'temba-checkbox'; attributes?: CheckboxAttributes }
|
|
74
|
+
| { type: 'temba-slider'; attributes?: SliderAttributes }
|
|
75
|
+
| { type: string; attributes?: { [key: string]: any } }; // Generic fallback
|
|
76
|
+
|
|
77
|
+
// Property configuration with the clean structure you want
|
|
78
|
+
export interface PropertyConfig {
|
|
79
|
+
// Form field metadata
|
|
80
|
+
label?: string;
|
|
81
|
+
helpText?: string;
|
|
82
|
+
required?: boolean;
|
|
83
|
+
maxLength?: number;
|
|
84
|
+
minLength?: number;
|
|
85
|
+
pattern?: string;
|
|
86
|
+
|
|
87
|
+
// Widget configuration
|
|
88
|
+
widget: WidgetConfig;
|
|
89
|
+
|
|
90
|
+
// Conditional behavior based on other field values
|
|
91
|
+
conditions?: {
|
|
92
|
+
// When to show this field
|
|
93
|
+
visible?: (formData: any) => boolean;
|
|
94
|
+
|
|
95
|
+
// When this field is disabled
|
|
96
|
+
disabled?: (formData: any) => boolean;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface NodeConfig {
|
|
101
|
+
type: string;
|
|
102
|
+
name?: string;
|
|
103
|
+
color?: string;
|
|
104
|
+
action?: ActionConfig;
|
|
105
|
+
router?: {
|
|
106
|
+
type: 'switch' | 'random';
|
|
107
|
+
defaultCategory?: string;
|
|
108
|
+
operand?: string;
|
|
109
|
+
configurable?: boolean; // can the rules be configured in the UI
|
|
110
|
+
rules?: {
|
|
111
|
+
type: 'has_number_between' | 'has_string' | 'has_value' | 'has_not_value';
|
|
112
|
+
arguments: string[];
|
|
113
|
+
categoryName: string;
|
|
114
|
+
}[];
|
|
115
|
+
};
|
|
116
|
+
properties?: { [key: string]: PropertyConfig };
|
|
117
|
+
toFormData?: (node: any) => any;
|
|
118
|
+
fromFormData?: (formData: any, originalNode: any) => any;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// New field configuration system for generic form generation
|
|
122
|
+
export interface BaseFieldConfig {
|
|
123
|
+
label?: string;
|
|
124
|
+
required?: boolean;
|
|
125
|
+
evaluated?: boolean; // if this field supports expression evaluation
|
|
126
|
+
dependsOn?: string[]; // fields this field depends on
|
|
127
|
+
computeValue?: (
|
|
128
|
+
values: Record<string, any>,
|
|
129
|
+
currentValue: any,
|
|
130
|
+
originalValues?: Record<string, any>
|
|
131
|
+
) => any;
|
|
132
|
+
|
|
133
|
+
// Validation properties
|
|
134
|
+
minLength?: number;
|
|
135
|
+
maxLength?: number;
|
|
136
|
+
pattern?: string;
|
|
137
|
+
helpText?: string;
|
|
138
|
+
|
|
139
|
+
// Layout properties
|
|
140
|
+
maxWidth?: string; // CSS max-width value (e.g., '200px', '50%', '10rem')
|
|
141
|
+
|
|
142
|
+
// Conditional rendering
|
|
143
|
+
conditions?: {
|
|
144
|
+
visible?: (formData: Record<string, any>) => boolean;
|
|
145
|
+
disabled?: (formData: Record<string, any>) => boolean;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface TextFieldConfig extends BaseFieldConfig {
|
|
150
|
+
type: 'text';
|
|
151
|
+
placeholder?: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface TextareaFieldConfig extends BaseFieldConfig {
|
|
155
|
+
type: 'textarea';
|
|
156
|
+
placeholder?: string;
|
|
157
|
+
rows?: number;
|
|
158
|
+
minHeight?: number;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface SelectFieldConfig extends BaseFieldConfig {
|
|
162
|
+
type: 'select';
|
|
163
|
+
options: string[] | { value: string; label: string }[];
|
|
164
|
+
multi?: boolean;
|
|
165
|
+
searchable?: boolean;
|
|
166
|
+
tags?: boolean;
|
|
167
|
+
placeholder?: string;
|
|
168
|
+
maxItems?: number;
|
|
169
|
+
valueKey?: string;
|
|
170
|
+
nameKey?: string;
|
|
171
|
+
endpoint?: string;
|
|
172
|
+
emails?: boolean;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface KeyValueFieldConfig extends BaseFieldConfig {
|
|
176
|
+
type: 'key-value';
|
|
177
|
+
sortable?: boolean;
|
|
178
|
+
keyPlaceholder?: string;
|
|
179
|
+
valuePlaceholder?: string;
|
|
180
|
+
minRows?: number;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface ArrayFieldConfig extends BaseFieldConfig {
|
|
184
|
+
type: 'array';
|
|
185
|
+
itemConfig: Record<string, FieldConfig>;
|
|
186
|
+
sortable?: boolean;
|
|
187
|
+
minItems?: number;
|
|
188
|
+
maxItems?: number;
|
|
189
|
+
itemLabel?: string;
|
|
190
|
+
onItemChange?: (
|
|
191
|
+
itemIndex: number,
|
|
192
|
+
field: string,
|
|
193
|
+
value: any,
|
|
194
|
+
allItems: any[]
|
|
195
|
+
) => any[];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface CheckboxFieldConfig extends BaseFieldConfig {
|
|
199
|
+
type: 'checkbox';
|
|
200
|
+
size?: number;
|
|
201
|
+
animateChange?: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export type FieldConfig =
|
|
205
|
+
| TextFieldConfig
|
|
206
|
+
| TextareaFieldConfig
|
|
207
|
+
| SelectFieldConfig
|
|
208
|
+
| KeyValueFieldConfig
|
|
209
|
+
| ArrayFieldConfig
|
|
210
|
+
| CheckboxFieldConfig;
|
|
211
|
+
|
|
212
|
+
// Layout configurations for better form organization
|
|
213
|
+
// Recursive layout system - any layout item can contain other layout items
|
|
214
|
+
|
|
215
|
+
export interface FieldItemConfig {
|
|
216
|
+
type: 'field';
|
|
217
|
+
field: string; // field name to render
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface RowLayoutConfig {
|
|
221
|
+
type: 'row';
|
|
222
|
+
items: LayoutItem[]; // can contain fields, groups, or other rows
|
|
223
|
+
gap?: string; // CSS gap value, defaults to '1rem'
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface GroupLayoutConfig {
|
|
227
|
+
type: 'group';
|
|
228
|
+
label: string;
|
|
229
|
+
items: LayoutItem[]; // can contain fields, rows, or other groups
|
|
230
|
+
collapsible?: boolean;
|
|
231
|
+
collapsed?: boolean; // initial state if collapsible
|
|
232
|
+
helpText?: string;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export type LayoutItem =
|
|
236
|
+
| FieldItemConfig
|
|
237
|
+
| RowLayoutConfig
|
|
238
|
+
| GroupLayoutConfig
|
|
239
|
+
| string; // string is shorthand for field
|
|
240
|
+
|
|
241
|
+
export interface ActionConfig {
|
|
242
|
+
name: string;
|
|
243
|
+
color: string;
|
|
244
|
+
evaluated?: string[];
|
|
245
|
+
render?: (node: any, action: any) => TemplateResult;
|
|
246
|
+
|
|
247
|
+
form?: Record<string, FieldConfig>;
|
|
248
|
+
layout?: LayoutItem[]; // optional layout configuration - array of layout items
|
|
249
|
+
|
|
250
|
+
// Action editor configuration (legacy)
|
|
251
|
+
// Form-level transformations
|
|
252
|
+
toFormData?: (action: Action) => any;
|
|
253
|
+
fromFormData?: (formData: any) => Action;
|
|
254
|
+
|
|
255
|
+
validate?: (action: Action) => ValidationResult;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export const COLORS = {
|
|
259
|
+
send: '#3498db',
|
|
260
|
+
update: '#01c1af',
|
|
261
|
+
broadcast: '#8e5ea7',
|
|
262
|
+
call: '#e68628',
|
|
263
|
+
create: '#df419f',
|
|
264
|
+
save: '#1a777c',
|
|
265
|
+
split: '#aaaaaa',
|
|
266
|
+
execute: '#666666',
|
|
267
|
+
wait: '#4d7dad',
|
|
268
|
+
add: '#309c42',
|
|
269
|
+
remove: '#e74c3c'
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Default property type mappings
|
|
273
|
+
export function getDefaultComponent(value: any): WidgetConfig['type'] {
|
|
274
|
+
if (typeof value === 'boolean') {
|
|
275
|
+
return 'temba-checkbox';
|
|
276
|
+
}
|
|
277
|
+
if (typeof value === 'number') {
|
|
278
|
+
return 'temba-textinput';
|
|
279
|
+
}
|
|
280
|
+
if (Array.isArray(value)) {
|
|
281
|
+
return 'temba-select'; // For arrays, use multi-select
|
|
282
|
+
}
|
|
283
|
+
// Default to text input for strings and unknown types
|
|
284
|
+
return 'temba-textinput';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Get component properties for default mappings with proper typing
|
|
288
|
+
export function getDefaultComponentProps(value: any): PropertyConfig {
|
|
289
|
+
if (typeof value === 'boolean') {
|
|
290
|
+
return {
|
|
291
|
+
widget: { type: 'temba-checkbox' }
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
if (typeof value === 'number') {
|
|
295
|
+
return {
|
|
296
|
+
widget: {
|
|
297
|
+
type: 'temba-textinput',
|
|
298
|
+
attributes: { type: 'number' }
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (Array.isArray(value)) {
|
|
303
|
+
if (value.length > 0 && typeof value[0] === 'string') {
|
|
304
|
+
return {
|
|
305
|
+
widget: {
|
|
306
|
+
type: 'temba-select',
|
|
307
|
+
attributes: { multi: true, tags: true }
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
widget: {
|
|
313
|
+
type: 'temba-select',
|
|
314
|
+
attributes: { multi: true }
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
widget: { type: 'temba-textinput' }
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Type guard functions for working with WidgetConfig
|
|
324
|
+
export function isTextInputWidget(
|
|
325
|
+
config: WidgetConfig
|
|
326
|
+
): config is { type: 'temba-textinput'; attributes?: TextInputAttributes } {
|
|
327
|
+
return config.type === 'temba-textinput';
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function isCompletionWidget(
|
|
331
|
+
config: WidgetConfig
|
|
332
|
+
): config is { type: 'temba-completion'; attributes?: CompletionAttributes } {
|
|
333
|
+
return config.type === 'temba-completion';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function isSelectWidget(
|
|
337
|
+
config: WidgetConfig
|
|
338
|
+
): config is { type: 'temba-select'; attributes?: SelectAttributes } {
|
|
339
|
+
return config.type === 'temba-select';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function isCheckboxWidget(
|
|
343
|
+
config: WidgetConfig
|
|
344
|
+
): config is { type: 'temba-checkbox'; attributes?: CheckboxAttributes } {
|
|
345
|
+
return config.type === 'temba-checkbox';
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export function isSliderWidget(
|
|
349
|
+
config: WidgetConfig
|
|
350
|
+
): config is { type: 'slider'; attributes?: SliderAttributes } {
|
|
351
|
+
return config.type === 'temba-slider';
|
|
352
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { html } from 'lit-html';
|
|
2
|
+
import { NamedObject } from '../store/flow-definition';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Renders a single line item with optional icon
|
|
6
|
+
*/
|
|
7
|
+
export const renderLineItem = (name: string, icon?: string) => {
|
|
8
|
+
return html`<div style="display:flex;items-align:center;">
|
|
9
|
+
${icon
|
|
10
|
+
? html`<temba-icon name=${icon} style="margin-right:0.5em"></temba-icon>`
|
|
11
|
+
: null}
|
|
12
|
+
<div
|
|
13
|
+
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 250px;"
|
|
14
|
+
>
|
|
15
|
+
${name}
|
|
16
|
+
</div>
|
|
17
|
+
</div>`;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Renders a list of named objects with optional icon, showing up to 3 items
|
|
22
|
+
* with a "+X more" indicator if there are more items
|
|
23
|
+
*/
|
|
24
|
+
export const renderNamedObjects = (assets: NamedObject[], icon?: string) => {
|
|
25
|
+
return renderStringList(
|
|
26
|
+
assets.map((asset) => asset.name),
|
|
27
|
+
icon
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Renders a list of strings with optional icon, showing up to 3 items
|
|
33
|
+
* with a "+X more" indicator if there are more items
|
|
34
|
+
*/
|
|
35
|
+
export const renderStringList = (items: string[], icon?: string) => {
|
|
36
|
+
const itemElements = [];
|
|
37
|
+
const maxDisplay = 3;
|
|
38
|
+
|
|
39
|
+
// Show up to 3 items, or all 4 if exactly 4 items
|
|
40
|
+
const displayCount =
|
|
41
|
+
items.length === 4 ? 4 : Math.min(maxDisplay, items.length);
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < displayCount; i++) {
|
|
44
|
+
const item = items[i];
|
|
45
|
+
itemElements.push(renderLineItem(item, icon));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Add "+X more" if there are more than 3 items (and not exactly 4)
|
|
49
|
+
if (items.length > maxDisplay && items.length !== 4) {
|
|
50
|
+
const remainingCount = items.length - maxDisplay;
|
|
51
|
+
itemElements.push(html`<div
|
|
52
|
+
style="display:flex;items-align:center;margin-top:0.2em;"
|
|
53
|
+
>
|
|
54
|
+
${icon
|
|
55
|
+
? html`<div style="margin-right:0.4em; width: 1em;"></div>` // spacing placeholder
|
|
56
|
+
: null}
|
|
57
|
+
<div style="font-size:0.8em">+${remainingCount} more</div>
|
|
58
|
+
</div>`);
|
|
59
|
+
}
|
|
60
|
+
return itemElements;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// URN scheme mapping for user-friendly display
|
|
64
|
+
export const urnSchemeMap: Record<string, string> = {
|
|
65
|
+
tel: 'Phone Number',
|
|
66
|
+
email: 'Email',
|
|
67
|
+
twitter: 'Twitter',
|
|
68
|
+
facebook: 'Facebook',
|
|
69
|
+
telegram: 'Telegram',
|
|
70
|
+
whatsapp: 'WhatsApp',
|
|
71
|
+
viber: 'Viber',
|
|
72
|
+
line: 'Line',
|
|
73
|
+
discord: 'Discord',
|
|
74
|
+
slack: 'Slack',
|
|
75
|
+
external: 'External ID'
|
|
76
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { html, css, TemplateResult } from 'lit';
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
+
import { FieldConfig } from '../flow/types';
|
|
4
|
+
import { BaseListEditor, ListItem } from './BaseListEditor';
|
|
5
|
+
|
|
6
|
+
@customElement('temba-array-editor')
|
|
7
|
+
export class TembaArrayEditor extends BaseListEditor<ListItem> {
|
|
8
|
+
@property({ type: Object })
|
|
9
|
+
itemConfig: Record<string, FieldConfig> = {};
|
|
10
|
+
|
|
11
|
+
@property({ type: String })
|
|
12
|
+
itemLabel = 'Item';
|
|
13
|
+
|
|
14
|
+
@property({ type: Function })
|
|
15
|
+
onItemChange?: (
|
|
16
|
+
itemIndex: number,
|
|
17
|
+
field: string,
|
|
18
|
+
value: any,
|
|
19
|
+
allItems: any[]
|
|
20
|
+
) => any[];
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
this._items = [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// External API
|
|
28
|
+
@property({ type: Array })
|
|
29
|
+
get value(): any[] {
|
|
30
|
+
return [...this._items];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
set value(newValue: any[]) {
|
|
34
|
+
this._items = newValue || [];
|
|
35
|
+
this.requestUpdate();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Implement abstract methods
|
|
39
|
+
isEmptyItem(item: ListItem): boolean {
|
|
40
|
+
return Object.values(item).every(
|
|
41
|
+
(value) => value === undefined || value === null || value === ''
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
createEmptyItem(): ListItem {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected handleFieldChange(
|
|
50
|
+
itemIndex: number,
|
|
51
|
+
fieldName: string,
|
|
52
|
+
newValue: any
|
|
53
|
+
) {
|
|
54
|
+
let updatedItems: any[];
|
|
55
|
+
|
|
56
|
+
if (this.onItemChange) {
|
|
57
|
+
updatedItems = this.onItemChange(
|
|
58
|
+
itemIndex,
|
|
59
|
+
fieldName,
|
|
60
|
+
newValue,
|
|
61
|
+
this._items
|
|
62
|
+
);
|
|
63
|
+
} else {
|
|
64
|
+
updatedItems = [...this._items];
|
|
65
|
+
updatedItems[itemIndex] = {
|
|
66
|
+
...updatedItems[itemIndex],
|
|
67
|
+
[fieldName]: newValue
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.updateValue(updatedItems);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private computeFieldValue(
|
|
75
|
+
itemIndex: number,
|
|
76
|
+
fieldName: string,
|
|
77
|
+
config: FieldConfig
|
|
78
|
+
): any {
|
|
79
|
+
const item = this._items[itemIndex] || {};
|
|
80
|
+
const currentValue = item[fieldName];
|
|
81
|
+
|
|
82
|
+
if (config.computeValue) {
|
|
83
|
+
return config.computeValue(item, currentValue);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return currentValue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private renderField(
|
|
90
|
+
itemIndex: number,
|
|
91
|
+
fieldName: string,
|
|
92
|
+
config: FieldConfig
|
|
93
|
+
): TemplateResult {
|
|
94
|
+
const computedValue = this.computeFieldValue(itemIndex, fieldName, config);
|
|
95
|
+
|
|
96
|
+
switch (config.type) {
|
|
97
|
+
case 'text':
|
|
98
|
+
return html`<temba-textinput
|
|
99
|
+
.value=${computedValue || ''}
|
|
100
|
+
.placeholder=${config.placeholder}
|
|
101
|
+
@change=${(e: any) =>
|
|
102
|
+
this.handleFieldChange(itemIndex, fieldName, e.target.value)}
|
|
103
|
+
></temba-textinput>`;
|
|
104
|
+
|
|
105
|
+
case 'textarea':
|
|
106
|
+
return html`<temba-textinput
|
|
107
|
+
.value=${computedValue || ''}
|
|
108
|
+
.placeholder=${config.placeholder}
|
|
109
|
+
textarea
|
|
110
|
+
.rows=${config.rows || 3}
|
|
111
|
+
@change=${(e: any) =>
|
|
112
|
+
this.handleFieldChange(itemIndex, fieldName, e.target.value)}
|
|
113
|
+
></temba-textinput>`;
|
|
114
|
+
|
|
115
|
+
case 'select':
|
|
116
|
+
return html`<temba-select
|
|
117
|
+
.value=${computedValue || ''}
|
|
118
|
+
.options=${config.options}
|
|
119
|
+
@change=${(e: any) =>
|
|
120
|
+
this.handleFieldChange(itemIndex, fieldName, e.target.value)}
|
|
121
|
+
></temba-select>`;
|
|
122
|
+
|
|
123
|
+
default:
|
|
124
|
+
return html`<span>Unsupported field type: ${config.type}</span>`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
renderItem(item: ListItem, index: number): TemplateResult {
|
|
129
|
+
const canRemove = this.canRemoveItem(index);
|
|
130
|
+
|
|
131
|
+
return html`
|
|
132
|
+
<div class="array-item">
|
|
133
|
+
<div class="item-header">
|
|
134
|
+
<span class="item-title">${this.itemLabel} ${index + 1}</span>
|
|
135
|
+
${canRemove
|
|
136
|
+
? html`
|
|
137
|
+
<button
|
|
138
|
+
@click=${() => this.removeItem(index)}
|
|
139
|
+
class="remove-btn"
|
|
140
|
+
>
|
|
141
|
+
Remove
|
|
142
|
+
</button>
|
|
143
|
+
`
|
|
144
|
+
: ''}
|
|
145
|
+
</div>
|
|
146
|
+
<div class="item-fields">
|
|
147
|
+
${Object.entries(this.itemConfig).map(
|
|
148
|
+
([fieldName, config]) => html`
|
|
149
|
+
<div class="field">
|
|
150
|
+
<label>${config.label}${config.required ? ' *' : ''}</label>
|
|
151
|
+
${this.renderField(index, fieldName, config)}
|
|
152
|
+
</div>
|
|
153
|
+
`
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
protected getContainerClass(): string {
|
|
161
|
+
return 'array-editor';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
protected renderAddButton(): TemplateResult {
|
|
165
|
+
return html`
|
|
166
|
+
<button class="add-btn" @click=${() => this.addItem()}>
|
|
167
|
+
Add ${this.itemLabel}
|
|
168
|
+
</button>
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
static styles = css`
|
|
173
|
+
.array-editor {
|
|
174
|
+
border: 1px solid #e0e0e0;
|
|
175
|
+
border-radius: 6px;
|
|
176
|
+
padding: 16px;
|
|
177
|
+
background: #fafafa;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.array-item {
|
|
181
|
+
border: 1px solid #d0d0d0;
|
|
182
|
+
border-radius: 4px;
|
|
183
|
+
padding: 16px;
|
|
184
|
+
margin-bottom: 12px;
|
|
185
|
+
background: white;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.item-header {
|
|
189
|
+
display: flex;
|
|
190
|
+
justify-content: space-between;
|
|
191
|
+
align-items: center;
|
|
192
|
+
margin-bottom: 12px;
|
|
193
|
+
padding-bottom: 8px;
|
|
194
|
+
border-bottom: 1px solid #eee;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.item-title {
|
|
198
|
+
font-weight: 600;
|
|
199
|
+
color: #333;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.item-fields {
|
|
203
|
+
display: grid;
|
|
204
|
+
gap: 12px;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.field label {
|
|
208
|
+
display: block;
|
|
209
|
+
margin-bottom: 4px;
|
|
210
|
+
font-weight: 500;
|
|
211
|
+
color: #555;
|
|
212
|
+
font-size: 14px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.add-btn,
|
|
216
|
+
.remove-btn {
|
|
217
|
+
padding: 8px 16px;
|
|
218
|
+
border: 1px solid #ccc;
|
|
219
|
+
border-radius: 4px;
|
|
220
|
+
background: white;
|
|
221
|
+
cursor: pointer;
|
|
222
|
+
font-size: 14px;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.add-btn:hover,
|
|
226
|
+
.remove-btn:hover {
|
|
227
|
+
background: #f8f8f8;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.remove-btn {
|
|
231
|
+
background: #fff5f5;
|
|
232
|
+
border-color: #fecaca;
|
|
233
|
+
color: #dc2626;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.remove-btn:hover {
|
|
237
|
+
background: #fef2f2;
|
|
238
|
+
}
|
|
239
|
+
`;
|
|
240
|
+
}
|