@nyaruka/temba-components 0.129.8 → 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/CHANGELOG.md +27 -3
- package/demo/data/flows/sample-flow.json +186 -96
- package/dist/temba-components.js +414 -351
- package/dist/temba-components.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 +133 -290
- 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 +1 -1
- 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/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 +18 -61
- package/out-tsc/src/form/ArrayEditor.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 +3 -3
- package/out-tsc/src/form/FormField.js.map +1 -1
- package/out-tsc/src/form/TextInput.js +1 -1
- package/out-tsc/src/form/TextInput.js.map +1 -1
- package/out-tsc/src/form/select/Select.js +48 -20
- package/out-tsc/src/form/select/Select.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +39 -13
- 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/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-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-node-editor.test.js +400 -0
- package/out-tsc/test/temba-node-editor.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +6 -3
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/temba-webchat.test.js +1 -1
- package/out-tsc/test/temba-webchat.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/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/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/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/editor/multiple-recipients.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/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/src/events.ts +8 -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 +186 -374
- 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 +1 -1
- package/src/flow/actions/open_ticket.ts +74 -3
- 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 +26 -127
- package/src/form/ArrayEditor.ts +34 -82
- package/src/form/FieldRenderer.ts +465 -0
- package/src/form/FormField.ts +3 -3
- package/src/form/TextInput.ts +1 -1
- package/src/form/select/Select.ts +51 -20
- package/src/live/ContactChat.ts +39 -15
- 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 +3 -3
- package/stress-test.js +18 -13
- 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-renderer.test.ts +482 -0
- package/test/temba-markdown.test.ts +1 -1
- package/test/temba-node-editor.test.ts +496 -0
- package/test/temba-select.test.ts +6 -6
- package/test/temba-webchat.test.ts +1 -1
- package/test-assets/select/llms.json +18 -0
- package/web-dev-mock.mjs +96 -6
- package/web-dev-server.config.mjs +29 -7
- package/test/temba-flow-editor.test.ts.backup +0 -563
- package/test/temba-utils-index.test.ts.backup +0 -1737
|
@@ -11,69 +11,4 @@ export const COLORS = {
|
|
|
11
11
|
add: '#309c42',
|
|
12
12
|
remove: '#e74c3c'
|
|
13
13
|
};
|
|
14
|
-
// Default property type mappings
|
|
15
|
-
export function getDefaultComponent(value) {
|
|
16
|
-
if (typeof value === 'boolean') {
|
|
17
|
-
return 'temba-checkbox';
|
|
18
|
-
}
|
|
19
|
-
if (typeof value === 'number') {
|
|
20
|
-
return 'temba-textinput';
|
|
21
|
-
}
|
|
22
|
-
if (Array.isArray(value)) {
|
|
23
|
-
return 'temba-select'; // For arrays, use multi-select
|
|
24
|
-
}
|
|
25
|
-
// Default to text input for strings and unknown types
|
|
26
|
-
return 'temba-textinput';
|
|
27
|
-
}
|
|
28
|
-
// Get component properties for default mappings with proper typing
|
|
29
|
-
export function getDefaultComponentProps(value) {
|
|
30
|
-
if (typeof value === 'boolean') {
|
|
31
|
-
return {
|
|
32
|
-
widget: { type: 'temba-checkbox' }
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
if (typeof value === 'number') {
|
|
36
|
-
return {
|
|
37
|
-
widget: {
|
|
38
|
-
type: 'temba-textinput',
|
|
39
|
-
attributes: { type: 'number' }
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
if (Array.isArray(value)) {
|
|
44
|
-
if (value.length > 0 && typeof value[0] === 'string') {
|
|
45
|
-
return {
|
|
46
|
-
widget: {
|
|
47
|
-
type: 'temba-select',
|
|
48
|
-
attributes: { multi: true, tags: true }
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
widget: {
|
|
54
|
-
type: 'temba-select',
|
|
55
|
-
attributes: { multi: true }
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
return {
|
|
60
|
-
widget: { type: 'temba-textinput' }
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
// Type guard functions for working with WidgetConfig
|
|
64
|
-
export function isTextInputWidget(config) {
|
|
65
|
-
return config.type === 'temba-textinput';
|
|
66
|
-
}
|
|
67
|
-
export function isCompletionWidget(config) {
|
|
68
|
-
return config.type === 'temba-completion';
|
|
69
|
-
}
|
|
70
|
-
export function isSelectWidget(config) {
|
|
71
|
-
return config.type === 'temba-select';
|
|
72
|
-
}
|
|
73
|
-
export function isCheckboxWidget(config) {
|
|
74
|
-
return config.type === 'temba-checkbox';
|
|
75
|
-
}
|
|
76
|
-
export function isSliderWidget(config) {
|
|
77
|
-
return config.type === 'temba-slider';
|
|
78
|
-
}
|
|
79
14
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/flow/types.ts"],"names":[],"mappings":"AAoRA,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,SAAS,EAAE,SAAS;IACpB,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE,SAAS;IAClB,IAAI,EAAE,SAAS;IACf,GAAG,EAAE,SAAS;IACd,MAAM,EAAE,SAAS;CAClB,CAAC;AAEF,iCAAiC;AACjC,MAAM,UAAU,mBAAmB,CAAC,KAAU;IAC5C,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,cAAc,CAAC,CAAC,+BAA+B;IACxD,CAAC;IACD,sDAAsD;IACtD,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,wBAAwB,CAAC,KAAU;IACjD,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO;YACL,MAAM,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;SACnC,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO;YACL,MAAM,EAAE;gBACN,IAAI,EAAE,iBAAiB;gBACvB,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;aAC/B;SACF,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;YACrD,OAAO;gBACL,MAAM,EAAE;oBACN,IAAI,EAAE,cAAc;oBACpB,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;iBACxC;aACF,CAAC;QACJ,CAAC;QACD,OAAO;YACL,MAAM,EAAE;gBACN,IAAI,EAAE,cAAc;gBACpB,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;aAC5B;SACF,CAAC;IACJ,CAAC;IACD,OAAO;QACL,MAAM,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE;KACpC,CAAC;AACJ,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,iBAAiB,CAC/B,MAAoB;IAEpB,OAAO,MAAM,CAAC,IAAI,KAAK,iBAAiB,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,MAAoB;IAEpB,OAAO,MAAM,CAAC,IAAI,KAAK,kBAAkB,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,MAAoB;IAEpB,OAAO,MAAM,CAAC,IAAI,KAAK,cAAc,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,MAAoB;IAEpB,OAAO,MAAM,CAAC,IAAI,KAAK,gBAAgB,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,MAAoB;IAEpB,OAAO,MAAM,CAAC,IAAI,KAAK,cAAc,CAAC;AACxC,CAAC","sourcesContent":["import { TemplateResult } from 'lit-html';\nimport { Action } from '../store/flow-definition';\n\nexport interface ValidationResult {\n valid: boolean;\n errors: { [key: string]: string };\n}\n\n// Component attribute interfaces - these define what's allowed for each component type\nexport interface TextInputAttributes {\n type?: 'text' | 'email' | 'number' | 'url' | 'tel';\n placeholder?: string;\n clearable?: boolean;\n maxlength?: number;\n gsm?: boolean;\n autogrow?: boolean;\n textarea?: boolean;\n submitOnEnter?: boolean;\n}\n\nexport interface CompletionAttributes {\n placeholder?: string;\n clearable?: boolean;\n maxlength?: number;\n gsm?: boolean;\n autogrow?: boolean;\n textarea?: boolean;\n expressions?: string;\n counter?: string;\n minHeight?: number;\n}\n\nexport interface SelectAttributes {\n placeholder?: string;\n multi?: boolean;\n searchable?: boolean;\n tags?: boolean;\n emails?: boolean;\n clearable?: boolean;\n endpoint?: string;\n valueKey?: string;\n nameKey?: string;\n queryParam?: string;\n maxItems?: number;\n maxItemsText?: string;\n expressions?: string;\n options?: Array<{ name: string; value: any }>;\n sorted?: boolean;\n allowCreate?: boolean;\n jsonValue?: boolean;\n spaceSelect?: boolean;\n infoText?: string;\n}\n\nexport interface CheckboxAttributes {\n label?: string;\n size?: number;\n disabled?: boolean;\n animateChange?: string;\n}\n\nexport interface SliderAttributes {\n min?: number;\n max?: number;\n range?: boolean;\n}\n\n// Widget configuration using discriminated union for type safety\nexport type WidgetConfig =\n | { type: 'temba-textinput'; attributes?: TextInputAttributes }\n | { type: 'temba-completion'; attributes?: CompletionAttributes }\n | { type: 'temba-select'; attributes?: SelectAttributes }\n | { type: 'temba-checkbox'; attributes?: CheckboxAttributes }\n | { type: 'temba-slider'; attributes?: SliderAttributes }\n | { type: string; attributes?: { [key: string]: any } }; // Generic fallback\n\n// Property configuration with the clean structure you want\nexport interface PropertyConfig {\n // Form field metadata\n label?: string;\n helpText?: string;\n required?: boolean;\n maxLength?: number;\n minLength?: number;\n pattern?: string;\n\n // Widget configuration\n widget: WidgetConfig;\n\n // Conditional behavior based on other field values\n conditions?: {\n // When to show this field\n visible?: (formData: any) => boolean;\n\n // When this field is disabled\n disabled?: (formData: any) => boolean;\n };\n}\n\nexport interface NodeConfig {\n type: string;\n name?: string;\n color?: string;\n action?: ActionConfig;\n router?: {\n type: 'switch' | 'random';\n defaultCategory?: string;\n operand?: string;\n configurable?: boolean; // can the rules be configured in the UI\n rules?: {\n type: 'has_number_between' | 'has_string' | 'has_value' | 'has_not_value';\n arguments: string[];\n categoryName: string;\n }[];\n };\n properties?: { [key: string]: PropertyConfig };\n toFormData?: (node: any) => any;\n fromFormData?: (formData: any, originalNode: any) => any;\n}\n\n// New field configuration system for generic form generation\nexport interface BaseFieldConfig {\n label?: string;\n required?: boolean;\n evaluated?: boolean; // if this field supports expression evaluation\n dependsOn?: string[]; // fields this field depends on\n computeValue?: (\n values: Record<string, any>,\n currentValue: any,\n originalValues?: Record<string, any>\n ) => any;\n\n // Validation properties\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n helpText?: string;\n\n // Layout properties\n maxWidth?: string; // CSS max-width value (e.g., '200px', '50%', '10rem')\n\n // Conditional rendering\n conditions?: {\n visible?: (formData: Record<string, any>) => boolean;\n disabled?: (formData: Record<string, any>) => boolean;\n };\n}\n\nexport interface TextFieldConfig extends BaseFieldConfig {\n type: 'text';\n placeholder?: string;\n}\n\nexport interface TextareaFieldConfig extends BaseFieldConfig {\n type: 'textarea';\n placeholder?: string;\n rows?: number;\n minHeight?: number;\n}\n\nexport interface SelectFieldConfig extends BaseFieldConfig {\n type: 'select';\n options: string[] | { value: string; label: string }[];\n multi?: boolean;\n clearable?: boolean;\n searchable?: boolean;\n tags?: boolean;\n placeholder?: string;\n maxItems?: number;\n valueKey?: string;\n nameKey?: string;\n endpoint?: string;\n emails?: boolean;\n flavor?: 'small' | 'large';\n}\n\nexport interface KeyValueFieldConfig extends BaseFieldConfig {\n type: 'key-value';\n sortable?: boolean;\n keyPlaceholder?: string;\n valuePlaceholder?: string;\n minRows?: number;\n}\n\nexport interface ArrayFieldConfig extends BaseFieldConfig {\n type: 'array';\n itemConfig: Record<string, FieldConfig>;\n sortable?: boolean;\n minItems?: number;\n maxItems?: number;\n itemLabel?: string;\n onItemChange?: (\n itemIndex: number,\n field: string,\n value: any,\n allItems: any[]\n ) => any[];\n isEmptyItem?: (item: any) => boolean;\n}\n\nexport interface CheckboxFieldConfig extends BaseFieldConfig {\n type: 'checkbox';\n size?: number;\n animateChange?: string;\n}\n\nexport interface MessageEditorFieldConfig extends BaseFieldConfig {\n type: 'message-editor';\n placeholder?: string;\n minHeight?: number;\n maxAttachments?: number;\n accept?: string;\n endpoint?: string;\n counter?: string;\n gsm?: boolean;\n autogrow?: boolean;\n disableCompletion?: boolean;\n}\n\nexport type FieldConfig =\n | TextFieldConfig\n | TextareaFieldConfig\n | SelectFieldConfig\n | KeyValueFieldConfig\n | ArrayFieldConfig\n | CheckboxFieldConfig\n | MessageEditorFieldConfig;\n\n// Layout configurations for better form organization\n// Recursive layout system - any layout item can contain other layout items\n\nexport interface FieldItemConfig {\n type: 'field';\n field: string; // field name to render\n}\n\nexport interface RowLayoutConfig {\n type: 'row';\n items: LayoutItem[]; // can contain fields, groups, or other rows\n gap?: string; // CSS gap value, defaults to '1rem'\n}\n\nexport interface GroupLayoutConfig {\n type: 'group';\n label: string;\n items: LayoutItem[]; // can contain fields, rows, or other groups\n collapsible?: boolean;\n collapsed?: boolean | ((formData: any) => boolean); // initial state if collapsible - can be a function\n helpText?: string;\n getGroupValueCount?: (formData: any) => number; // optional function to get count for bubble display\n}\n\nexport type LayoutItem =\n | FieldItemConfig\n | RowLayoutConfig\n | GroupLayoutConfig\n | string; // string is shorthand for field\n\nexport interface ActionConfig {\n name: string;\n color: string;\n evaluated?: string[];\n render?: (node: any, action: any) => TemplateResult;\n\n form?: Record<string, FieldConfig>;\n layout?: LayoutItem[]; // optional layout configuration - array of layout items\n\n // Action editor configuration (legacy)\n // Form-level transformations\n sanitize?: (formData: any) => any;\n toFormData?: (action: Action) => any;\n fromFormData?: (formData: any) => Action;\n\n validate?: (action: Action) => ValidationResult;\n}\n\nexport const COLORS = {\n send: '#3498db',\n update: '#01c1af',\n broadcast: '#8e5ea7',\n call: '#e68628',\n create: '#df419f',\n save: '#1a777c',\n split: '#aaaaaa',\n execute: '#666666',\n wait: '#4d7dad',\n add: '#309c42',\n remove: '#e74c3c'\n};\n\n// Default property type mappings\nexport function getDefaultComponent(value: any): WidgetConfig['type'] {\n if (typeof value === 'boolean') {\n return 'temba-checkbox';\n }\n if (typeof value === 'number') {\n return 'temba-textinput';\n }\n if (Array.isArray(value)) {\n return 'temba-select'; // For arrays, use multi-select\n }\n // Default to text input for strings and unknown types\n return 'temba-textinput';\n}\n\n// Get component properties for default mappings with proper typing\nexport function getDefaultComponentProps(value: any): PropertyConfig {\n if (typeof value === 'boolean') {\n return {\n widget: { type: 'temba-checkbox' }\n };\n }\n if (typeof value === 'number') {\n return {\n widget: {\n type: 'temba-textinput',\n attributes: { type: 'number' }\n }\n };\n }\n if (Array.isArray(value)) {\n if (value.length > 0 && typeof value[0] === 'string') {\n return {\n widget: {\n type: 'temba-select',\n attributes: { multi: true, tags: true }\n }\n };\n }\n return {\n widget: {\n type: 'temba-select',\n attributes: { multi: true }\n }\n };\n }\n return {\n widget: { type: 'temba-textinput' }\n };\n}\n\n// Type guard functions for working with WidgetConfig\nexport function isTextInputWidget(\n config: WidgetConfig\n): config is { type: 'temba-textinput'; attributes?: TextInputAttributes } {\n return config.type === 'temba-textinput';\n}\n\nexport function isCompletionWidget(\n config: WidgetConfig\n): config is { type: 'temba-completion'; attributes?: CompletionAttributes } {\n return config.type === 'temba-completion';\n}\n\nexport function isSelectWidget(\n config: WidgetConfig\n): config is { type: 'temba-select'; attributes?: SelectAttributes } {\n return config.type === 'temba-select';\n}\n\nexport function isCheckboxWidget(\n config: WidgetConfig\n): config is { type: 'temba-checkbox'; attributes?: CheckboxAttributes } {\n return config.type === 'temba-checkbox';\n}\n\nexport function isSliderWidget(\n config: WidgetConfig\n): config is { type: 'slider'; attributes?: SliderAttributes } {\n return config.type === 'temba-slider';\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/flow/types.ts"],"names":[],"mappings":"AAiQA,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,SAAS,EAAE,SAAS;IACpB,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE,SAAS;IAClB,IAAI,EAAE,SAAS;IACf,GAAG,EAAE,SAAS;IACd,MAAM,EAAE,SAAS;CAClB,CAAC","sourcesContent":["import { TemplateResult } from 'lit-html';\nimport { Action, Node } from '../store/flow-definition';\n\nexport interface ValidationResult {\n valid: boolean;\n errors: { [key: string]: string };\n}\n\n// Component attribute interfaces - these define what's allowed for each component type\nexport interface TextInputAttributes {\n type?: 'text' | 'email' | 'number' | 'url' | 'tel';\n placeholder?: string;\n clearable?: boolean;\n maxlength?: number;\n gsm?: boolean;\n autogrow?: boolean;\n textarea?: boolean;\n submitOnEnter?: boolean;\n}\n\nexport interface CompletionAttributes {\n placeholder?: string;\n clearable?: boolean;\n maxlength?: number;\n gsm?: boolean;\n autogrow?: boolean;\n textarea?: boolean;\n expressions?: string;\n counter?: string;\n minHeight?: number;\n}\n\nexport interface SelectAttributes {\n placeholder?: string;\n multi?: boolean;\n searchable?: boolean;\n tags?: boolean;\n emails?: boolean;\n clearable?: boolean;\n endpoint?: string;\n valueKey?: string;\n nameKey?: string;\n queryParam?: string;\n maxItems?: number;\n maxItemsText?: string;\n expressions?: string;\n options?: Array<{ name: string; value: any }>;\n sorted?: boolean;\n allowCreate?: boolean;\n jsonValue?: boolean;\n spaceSelect?: boolean;\n infoText?: string;\n}\n\nexport interface CheckboxAttributes {\n label?: string;\n size?: number;\n disabled?: boolean;\n animateChange?: string;\n}\n\nexport interface SliderAttributes {\n min?: number;\n max?: number;\n range?: boolean;\n}\n\nexport interface FormData extends Record<string, any> {}\n\nexport interface FormConfig {\n form?: Record<string, FieldConfig>;\n layout?: LayoutItem[];\n sanitize?: (formData: FormData) => void;\n validate?: (formData: FormData) => ValidationResult;\n}\n\nexport interface NodeConfig extends FormConfig {\n type: string;\n name?: string;\n color?: string;\n action?: ActionConfig;\n router?: {\n type: 'switch' | 'random';\n defaultCategory?: string;\n operand?: string;\n configurable?: boolean; // can the rules be configured in the UI\n rules?: {\n type:\n | 'has_number_between'\n | 'has_string'\n | 'has_value'\n | 'has_not_value'\n | 'has_text';\n arguments: string[];\n categoryName: string;\n }[];\n };\n\n toFormData?: (node: Node) => FormData;\n fromFormData?: (formData: FormData, originalNode: Node) => Node;\n render?: (node: Node) => TemplateResult;\n}\n\n// New field configuration system for generic form generation\nexport interface BaseFieldConfig {\n label?: string;\n required?: boolean;\n evaluated?: boolean;\n dependsOn?: string[];\n computeValue?: (\n values: Record<string, any>,\n currentValue: any,\n originalValues?: Record<string, any>\n ) => any;\n\n // Validation properties\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n helpText?: string;\n\n // Layout properties\n maxWidth?: string;\n\n // Conditional rendering\n conditions?: {\n visible?: (formData: Record<string, any>) => boolean;\n disabled?: (formData: Record<string, any>) => boolean;\n };\n}\n\nexport interface TextFieldConfig extends BaseFieldConfig {\n type: 'text';\n placeholder?: string;\n}\n\nexport interface TextareaFieldConfig extends BaseFieldConfig {\n type: 'textarea';\n placeholder?: string;\n rows?: number;\n minHeight?: number;\n}\n\nexport interface SelectFieldConfig extends BaseFieldConfig {\n type: 'select';\n options?: string[] | { value: string; label: string }[];\n multi?: boolean;\n clearable?: boolean;\n searchable?: boolean;\n tags?: boolean;\n placeholder?: string;\n maxItems?: number;\n valueKey?: string;\n nameKey?: string;\n endpoint?: string;\n emails?: boolean;\n getName?: (item: any) => string;\n flavor?: 'small' | 'large';\n createArbitraryOption?: (input: string, options: any[]) => any;\n allowCreate?: boolean;\n}\n\nexport interface KeyValueFieldConfig extends BaseFieldConfig {\n type: 'key-value';\n sortable?: boolean;\n keyPlaceholder?: string;\n valuePlaceholder?: string;\n minRows?: number;\n}\n\nexport interface ArrayFieldConfig extends BaseFieldConfig {\n type: 'array';\n itemConfig: Record<string, FieldConfig>;\n sortable?: boolean;\n minItems?: number;\n maxItems?: number;\n itemLabel?: string;\n onItemChange?: (\n itemIndex: number,\n field: string,\n value: any,\n allItems: any[]\n ) => any[];\n isEmptyItem?: (item: any) => boolean;\n}\n\nexport interface CheckboxFieldConfig extends BaseFieldConfig {\n type: 'checkbox';\n size?: number;\n animateChange?: string;\n}\n\nexport interface MessageEditorFieldConfig extends BaseFieldConfig {\n type: 'message-editor';\n placeholder?: string;\n minHeight?: number;\n maxAttachments?: number;\n accept?: string;\n endpoint?: string;\n counter?: string;\n gsm?: boolean;\n autogrow?: boolean;\n disableCompletion?: boolean;\n}\n\nexport type FieldConfig =\n | TextFieldConfig\n | TextareaFieldConfig\n | SelectFieldConfig\n | KeyValueFieldConfig\n | ArrayFieldConfig\n | CheckboxFieldConfig\n | MessageEditorFieldConfig;\n\n// Layout configurations for better form organization\n// Recursive layout system - any layout item can contain other layout items\n\nexport interface FieldItemConfig {\n type: 'field';\n field: string; // field name to render\n}\n\nexport interface RowLayoutConfig {\n type: 'row';\n items: LayoutItem[]; // can contain fields, groups, or other rows\n gap?: string; // CSS gap value, defaults to '1rem'\n}\n\nexport interface GroupLayoutConfig {\n type: 'group';\n label: string;\n items: LayoutItem[]; // can contain fields, rows, or other groups\n collapsible?: boolean;\n collapsed?: boolean | ((formData: any) => boolean); // initial state if collapsible - can be a function\n helpText?: string;\n getGroupValueCount?: (formData: any) => number; // optional function to get count for bubble display\n}\n\nexport type LayoutItem =\n | FieldItemConfig\n | RowLayoutConfig\n | GroupLayoutConfig\n | string; // string is shorthand for field\n\nexport interface ActionConfig extends FormConfig {\n name: string;\n color: string;\n evaluated?: string[];\n render?: (node: any, action: any) => TemplateResult;\n\n form?: Record<string, FieldConfig>;\n layout?: LayoutItem[]; // optional layout configuration - array of layout items\n\n toFormData?: (action: Action) => any;\n fromFormData?: (formData: any) => Action;\n}\n\nexport const COLORS = {\n send: '#3498db',\n update: '#01c1af',\n broadcast: '#8e5ea7',\n call: '#e68628',\n create: '#df419f',\n save: '#1a777c',\n split: '#aaaaaa',\n execute: '#666666',\n wait: '#4d7dad',\n add: '#309c42',\n remove: '#e74c3c'\n};\n"]}
|
|
@@ -2,6 +2,7 @@ import { __decorate } from "tslib";
|
|
|
2
2
|
import { html, css } from 'lit';
|
|
3
3
|
import { customElement, property } from 'lit/decorators.js';
|
|
4
4
|
import { BaseListEditor } from './BaseListEditor';
|
|
5
|
+
import { FieldRenderer } from './FieldRenderer';
|
|
5
6
|
let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
|
|
6
7
|
constructor() {
|
|
7
8
|
super();
|
|
@@ -73,43 +74,17 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
|
|
|
73
74
|
return currentValue;
|
|
74
75
|
}
|
|
75
76
|
renderField(itemIndex, fieldName, config) {
|
|
76
|
-
var _a;
|
|
77
77
|
const computedValue = this.computeFieldValue(itemIndex, fieldName, config);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
.placeholder=${config.placeholder}
|
|
89
|
-
textarea
|
|
90
|
-
.rows=${config.rows || 3}
|
|
91
|
-
@change=${(e) => this.handleFieldChange(itemIndex, fieldName, e.target.value)}
|
|
92
|
-
></temba-textinput>`;
|
|
93
|
-
case 'select': {
|
|
94
|
-
const selectConfig = config;
|
|
95
|
-
const fieldValue = this.computeFieldValue(itemIndex, fieldName, config);
|
|
96
|
-
return html `<temba-select
|
|
97
|
-
class="form-control"
|
|
98
|
-
?clearable="${selectConfig.clearable || false}"
|
|
99
|
-
?searchable="${selectConfig.searchable || false}"
|
|
100
|
-
?tags="${selectConfig.tags || false}"
|
|
101
|
-
?multi="${selectConfig.multi || false}"
|
|
102
|
-
?emails="${selectConfig.emails || false}"
|
|
103
|
-
placeholder="${selectConfig.placeholder || ''}"
|
|
104
|
-
maxItems="${selectConfig.maxItems || 0}"
|
|
105
|
-
valueKey="${selectConfig.valueKey || 'value'}"
|
|
106
|
-
nameKey="${selectConfig.nameKey || 'name'}"
|
|
107
|
-
endpoint="${selectConfig.endpoint || ''}"
|
|
108
|
-
value="${fieldValue || ''}"
|
|
109
|
-
flavor="small"
|
|
110
|
-
@change="${(e) => {
|
|
111
|
-
const target = e.target;
|
|
112
|
-
let value;
|
|
78
|
+
// Use FieldRenderer for consistent field rendering
|
|
79
|
+
return FieldRenderer.renderField(fieldName, config, computedValue, {
|
|
80
|
+
showLabel: false, // ArrayEditor doesn't show labels for individual fields
|
|
81
|
+
flavor: 'small', // ArrayEditor uses small flavor
|
|
82
|
+
extraClasses: 'form-control',
|
|
83
|
+
onChange: (e) => {
|
|
84
|
+
let value;
|
|
85
|
+
const target = e.target;
|
|
86
|
+
// Handle different field types and their change events
|
|
87
|
+
if (config.type === 'select') {
|
|
113
88
|
// For temba-select, extract the correct value
|
|
114
89
|
if (target.tagName === 'TEMBA-SELECT') {
|
|
115
90
|
if (target.multi || target.emails || target.tags) {
|
|
@@ -129,28 +104,14 @@ let TembaArrayEditor = class TembaArrayEditor extends BaseListEditor {
|
|
|
129
104
|
else {
|
|
130
105
|
value = target.value;
|
|
131
106
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
name="${option}"
|
|
139
|
-
value="${option}"
|
|
140
|
-
></temba-option>`;
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
return html `<temba-option
|
|
144
|
-
name="${option.label || option.name}"
|
|
145
|
-
value="${option.value}"
|
|
146
|
-
></temba-option>`;
|
|
147
|
-
}
|
|
148
|
-
})}
|
|
149
|
-
</temba-select>`;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// For other field types, use the target value directly
|
|
110
|
+
value = target.value;
|
|
111
|
+
}
|
|
112
|
+
this.handleFieldChange(itemIndex, fieldName, value);
|
|
150
113
|
}
|
|
151
|
-
|
|
152
|
-
return html `<span>Unsupported field type: ${config.type}</span>`;
|
|
153
|
-
}
|
|
114
|
+
});
|
|
154
115
|
}
|
|
155
116
|
renderItem(item, index) {
|
|
156
117
|
const canRemove = this.canRemoveItem(index);
|
|
@@ -215,10 +176,6 @@ TembaArrayEditor.styles = css `
|
|
|
215
176
|
flex: 1;
|
|
216
177
|
}
|
|
217
178
|
|
|
218
|
-
.field:first-child {
|
|
219
|
-
flex: 0 0 140px; /* Fixed width for type dropdown */
|
|
220
|
-
}
|
|
221
|
-
|
|
222
179
|
.field label {
|
|
223
180
|
display: block;
|
|
224
181
|
margin-bottom: 4px;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ArrayEditor.js","sourceRoot":"","sources":["../../../src/form/ArrayEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, TemplateResult } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { FieldConfig, SelectFieldConfig } from '../flow/types';\nimport { BaseListEditor, ListItem } from './BaseListEditor';\n\n@customElement('temba-array-editor')\nexport class TembaArrayEditor extends BaseListEditor<ListItem> {\n @property({ type: Object })\n itemConfig: Record<string, FieldConfig> = {};\n\n @property({ type: String })\n itemLabel = 'Item';\n\n @property({ type: Function })\n onItemChange?: (\n itemIndex: number,\n field: string,\n value: any,\n allItems: any[]\n ) => any[];\n\n @property({ type: Function })\n isEmptyItemFn?: (item: any) => boolean;\n\n @property({ type: Boolean })\n maintainEmptyItem = true; // Enable by default for better UX\n\n constructor() {\n super();\n this._items = [];\n }\n\n // External API\n @property({ type: Array })\n get value(): any[] {\n return [...this._items];\n }\n\n set value(newValue: any[]) {\n this._items = newValue || [];\n this.requestUpdate();\n }\n\n // Implement abstract methods\n isEmptyItem(item: ListItem): boolean {\n // Use configurable function if provided\n if (this.isEmptyItemFn) {\n return this.isEmptyItemFn(item);\n }\n\n // Default behavior: check if all values are empty\n const values = Object.values(item);\n if (values.length === 0) {\n return true;\n }\n\n return values.every(\n (value) => value === undefined || value === null || value === ''\n );\n }\n\n // Override cleanItems to be more permissive for form data\n protected cleanItems(items: ListItem[]): any {\n // For runtime attachments, keep items that have at least one non-empty field\n return items.filter((item) => {\n const values = Object.values(item);\n return (\n values.length > 0 &&\n values.some(\n (value) => value !== undefined && value !== null && value !== ''\n )\n );\n });\n }\n\n createEmptyItem(): ListItem {\n return {};\n }\n\n protected handleFieldChange(\n itemIndex: number,\n fieldName: string,\n newValue: any\n ) {\n let updatedItems: any[];\n\n if (this.onItemChange) {\n updatedItems = this.onItemChange(\n itemIndex,\n fieldName,\n newValue,\n this._items\n );\n } else {\n updatedItems = [...this._items];\n updatedItems[itemIndex] = {\n ...updatedItems[itemIndex],\n [fieldName]: newValue\n };\n }\n\n this.updateValue(updatedItems);\n }\n\n private computeFieldValue(\n itemIndex: number,\n fieldName: string,\n config: FieldConfig\n ): any {\n const item = this._items[itemIndex] || {};\n const currentValue = item[fieldName];\n\n if (config.computeValue) {\n return config.computeValue(item, currentValue);\n }\n\n // For select fields, ensure we return the right type\n if (config.type === 'select') {\n const selectConfig = config as SelectFieldConfig;\n if (currentValue === undefined || currentValue === null) {\n return selectConfig.multi ? [] : '';\n }\n }\n\n return currentValue;\n }\n\n private renderField(\n itemIndex: number,\n fieldName: string,\n config: FieldConfig\n ): TemplateResult {\n const computedValue = this.computeFieldValue(itemIndex, fieldName, config);\n\n switch (config.type) {\n case 'text':\n return html`<temba-textinput\n .value=${computedValue || ''}\n .placeholder=${config.placeholder}\n @change=${(e: any) =>\n this.handleFieldChange(itemIndex, fieldName, e.target.value)}\n ></temba-textinput>`;\n\n case 'textarea':\n return html`<temba-textinput\n .value=${computedValue || ''}\n .placeholder=${config.placeholder}\n textarea\n .rows=${config.rows || 3}\n @change=${(e: any) =>\n this.handleFieldChange(itemIndex, fieldName, e.target.value)}\n ></temba-textinput>`;\n\n case 'select': {\n const selectConfig = config as SelectFieldConfig;\n const fieldValue = this.computeFieldValue(itemIndex, fieldName, config);\n\n return html`<temba-select\n class=\"form-control\"\n ?clearable=\"${selectConfig.clearable || false}\"\n ?searchable=\"${selectConfig.searchable || false}\"\n ?tags=\"${selectConfig.tags || false}\"\n ?multi=\"${selectConfig.multi || false}\"\n ?emails=\"${selectConfig.emails || false}\"\n placeholder=\"${selectConfig.placeholder || ''}\"\n maxItems=\"${selectConfig.maxItems || 0}\"\n valueKey=\"${selectConfig.valueKey || 'value'}\"\n nameKey=\"${selectConfig.nameKey || 'name'}\"\n endpoint=\"${selectConfig.endpoint || ''}\"\n value=\"${fieldValue || ''}\"\n flavor=\"small\"\n @change=\"${(e: Event) => {\n const target = e.target as any;\n let value: any;\n\n // For temba-select, extract the correct value\n if (target.tagName === 'TEMBA-SELECT') {\n if (target.multi || target.emails || target.tags) {\n value = target.values || [];\n } else {\n // Single select: extract value from first selected option\n const values = target.values || [];\n value =\n values.length > 0 && values[0]\n ? values[0].value !== undefined\n ? values[0].value\n : values[0]\n : '';\n }\n } else {\n value = target.value;\n }\n\n this.handleFieldChange(itemIndex, fieldName, value);\n }}\"\n >\n ${selectConfig.options?.map((option: any) => {\n if (typeof option === 'string') {\n return html`<temba-option\n name=\"${option}\"\n value=\"${option}\"\n ></temba-option>`;\n } else {\n return html`<temba-option\n name=\"${option.label || option.name}\"\n value=\"${option.value}\"\n ></temba-option>`;\n }\n })}\n </temba-select>`;\n }\n\n default:\n return html`<span>Unsupported field type: ${config.type}</span>`;\n }\n }\n\n renderItem(item: ListItem, index: number): TemplateResult {\n const canRemove = this.canRemoveItem(index);\n\n return html`\n <div class=\"array-item\">\n <div class=\"item-fields\">\n ${Object.entries(this.itemConfig).map(\n ([fieldName, config]) => html`\n <div class=\"field\">\n ${this.renderField(index, fieldName, config)}\n </div>\n `\n )}\n ${canRemove\n ? html`\n <button\n @click=${() => this.removeItem(index)}\n class=\"remove-btn\"\n >\n <temba-icon name=\"x\"></temba-icon>\n </button>\n `\n : ''}\n </div>\n </div>\n `;\n }\n\n protected getContainerClass(): string {\n return 'array-editor';\n }\n\n protected renderAddButton(): TemplateResult {\n return html`\n <button class=\"add-btn\" @click=${() => this.addItem()}>\n Add ${this.itemLabel}\n </button>\n `;\n }\n\n static styles = css`\n .array-editor {\n }\n\n .array-item {\n }\n\n .item-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .item-title {\n font-weight: 600;\n color: #333;\n }\n\n .item-fields {\n display: flex;\n gap: 12px;\n align-items: center;\n }\n\n .field {\n flex: 1;\n }\n\n .field:first-child {\n flex: 0 0 140px; /* Fixed width for type dropdown */\n }\n\n .field label {\n display: block;\n margin-bottom: 4px;\n font-weight: 500;\n color: #555;\n font-size: 14px;\n }\n\n .add-btn,\n .remove-btn {\n padding: 8px;\n border: 1px solid #ccc;\n border-radius: 4px;\n background: white;\n cursor: pointer;\n font-size: 14px;\n }\n\n .add-btn:hover,\n .remove-btn:hover {\n background: #f8f8f8;\n }\n\n .remove-btn {\n background: #fefefe;\n color: #999;\n }\n `;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ArrayEditor.js","sourceRoot":"","sources":["../../../src/form/ArrayEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, TemplateResult } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { FieldConfig, SelectFieldConfig } from '../flow/types';\nimport { BaseListEditor, ListItem } from './BaseListEditor';\nimport { FieldRenderer } from './FieldRenderer';\n\n@customElement('temba-array-editor')\nexport class TembaArrayEditor extends BaseListEditor<ListItem> {\n @property({ type: Object })\n itemConfig: Record<string, FieldConfig> = {};\n\n @property({ type: String })\n itemLabel = 'Item';\n\n @property({ type: Function })\n onItemChange?: (\n itemIndex: number,\n field: string,\n value: any,\n allItems: any[]\n ) => any[];\n\n @property({ type: Function })\n isEmptyItemFn?: (item: any) => boolean;\n\n @property({ type: Boolean })\n maintainEmptyItem = true; // Enable by default for better UX\n\n constructor() {\n super();\n this._items = [];\n }\n\n // External API\n @property({ type: Array })\n get value(): any[] {\n return [...this._items];\n }\n\n set value(newValue: any[]) {\n this._items = newValue || [];\n this.requestUpdate();\n }\n\n // Implement abstract methods\n isEmptyItem(item: ListItem): boolean {\n // Use configurable function if provided\n if (this.isEmptyItemFn) {\n return this.isEmptyItemFn(item);\n }\n\n // Default behavior: check if all values are empty\n const values = Object.values(item);\n if (values.length === 0) {\n return true;\n }\n\n return values.every(\n (value) => value === undefined || value === null || value === ''\n );\n }\n\n // Override cleanItems to be more permissive for form data\n protected cleanItems(items: ListItem[]): any {\n // For runtime attachments, keep items that have at least one non-empty field\n return items.filter((item) => {\n const values = Object.values(item);\n return (\n values.length > 0 &&\n values.some(\n (value) => value !== undefined && value !== null && value !== ''\n )\n );\n });\n }\n\n createEmptyItem(): ListItem {\n return {};\n }\n\n protected handleFieldChange(\n itemIndex: number,\n fieldName: string,\n newValue: any\n ) {\n let updatedItems: any[];\n\n if (this.onItemChange) {\n updatedItems = this.onItemChange(\n itemIndex,\n fieldName,\n newValue,\n this._items\n );\n } else {\n updatedItems = [...this._items];\n updatedItems[itemIndex] = {\n ...updatedItems[itemIndex],\n [fieldName]: newValue\n };\n }\n\n this.updateValue(updatedItems);\n }\n\n private computeFieldValue(\n itemIndex: number,\n fieldName: string,\n config: FieldConfig\n ): any {\n const item = this._items[itemIndex] || {};\n const currentValue = item[fieldName];\n\n if (config.computeValue) {\n return config.computeValue(item, currentValue);\n }\n\n // For select fields, ensure we return the right type\n if (config.type === 'select') {\n const selectConfig = config as SelectFieldConfig;\n if (currentValue === undefined || currentValue === null) {\n return selectConfig.multi ? [] : '';\n }\n }\n\n return currentValue;\n }\n\n private renderField(\n itemIndex: number,\n fieldName: string,\n config: FieldConfig\n ): TemplateResult {\n const computedValue = this.computeFieldValue(itemIndex, fieldName, config);\n\n // Use FieldRenderer for consistent field rendering\n return FieldRenderer.renderField(fieldName, config, computedValue, {\n showLabel: false, // ArrayEditor doesn't show labels for individual fields\n flavor: 'small', // ArrayEditor uses small flavor\n extraClasses: 'form-control',\n onChange: (e: Event) => {\n let value: any;\n const target = e.target as any;\n\n // Handle different field types and their change events\n if (config.type === 'select') {\n // For temba-select, extract the correct value\n if (target.tagName === 'TEMBA-SELECT') {\n if (target.multi || target.emails || target.tags) {\n value = target.values || [];\n } else {\n // Single select: extract value from first selected option\n const values = target.values || [];\n value =\n values.length > 0 && values[0]\n ? values[0].value !== undefined\n ? values[0].value\n : values[0]\n : '';\n }\n } else {\n value = target.value;\n }\n } else {\n // For other field types, use the target value directly\n value = target.value;\n }\n\n this.handleFieldChange(itemIndex, fieldName, value);\n }\n });\n }\n\n renderItem(item: ListItem, index: number): TemplateResult {\n const canRemove = this.canRemoveItem(index);\n\n return html`\n <div class=\"array-item\">\n <div class=\"item-fields\">\n ${Object.entries(this.itemConfig).map(\n ([fieldName, config]) => html`\n <div class=\"field\">\n ${this.renderField(index, fieldName, config)}\n </div>\n `\n )}\n ${canRemove\n ? html`\n <button\n @click=${() => this.removeItem(index)}\n class=\"remove-btn\"\n >\n <temba-icon name=\"x\"></temba-icon>\n </button>\n `\n : ''}\n </div>\n </div>\n `;\n }\n\n protected getContainerClass(): string {\n return 'array-editor';\n }\n\n protected renderAddButton(): TemplateResult {\n return html`\n <button class=\"add-btn\" @click=${() => this.addItem()}>\n Add ${this.itemLabel}\n </button>\n `;\n }\n\n static styles = css`\n .array-editor {\n }\n\n .array-item {\n }\n\n .item-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .item-title {\n font-weight: 600;\n color: #333;\n }\n\n .item-fields {\n display: flex;\n gap: 12px;\n align-items: center;\n }\n\n .field {\n flex: 1;\n }\n\n .field label {\n display: block;\n margin-bottom: 4px;\n font-weight: 500;\n color: #555;\n font-size: 14px;\n }\n\n .add-btn,\n .remove-btn {\n padding: 8px;\n border: 1px solid #ccc;\n border-radius: 4px;\n background: white;\n cursor: pointer;\n font-size: 14px;\n }\n\n .add-btn:hover,\n .remove-btn:hover {\n background: #f8f8f8;\n }\n\n .remove-btn {\n background: #fefefe;\n color: #999;\n }\n `;\n}\n"]}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
/**
|
|
3
|
+
* FieldRenderer provides a consistent way to render field configurations
|
|
4
|
+
* into web components across different contexts (NodeEditor, ArrayEditor, etc.)
|
|
5
|
+
*/
|
|
6
|
+
export class FieldRenderer {
|
|
7
|
+
/**
|
|
8
|
+
* Renders a field based on its configuration
|
|
9
|
+
* @param fieldName - The name of the field
|
|
10
|
+
* @param config - The field configuration
|
|
11
|
+
* @param value - The current value of the field
|
|
12
|
+
* @param context - Additional context for rendering
|
|
13
|
+
* @returns A TemplateResult for the rendered field
|
|
14
|
+
*/
|
|
15
|
+
static renderField(fieldName, config, value, context = {}) {
|
|
16
|
+
/*const {
|
|
17
|
+
errors = [],
|
|
18
|
+
onChange,
|
|
19
|
+
showLabel = true,
|
|
20
|
+
flavor,
|
|
21
|
+
extraClasses = '',
|
|
22
|
+
style = ''
|
|
23
|
+
} = context;*/
|
|
24
|
+
switch (config.type) {
|
|
25
|
+
case 'text':
|
|
26
|
+
return FieldRenderer.renderTextInput(fieldName, config, value, context);
|
|
27
|
+
case 'textarea':
|
|
28
|
+
return FieldRenderer.renderTextarea(fieldName, config, value, context);
|
|
29
|
+
case 'select':
|
|
30
|
+
return FieldRenderer.renderSelect(fieldName, config, value, context);
|
|
31
|
+
case 'checkbox':
|
|
32
|
+
return FieldRenderer.renderCheckbox(fieldName, config, value, context);
|
|
33
|
+
case 'key-value':
|
|
34
|
+
return FieldRenderer.renderKeyValue(fieldName, config, value, context);
|
|
35
|
+
case 'array':
|
|
36
|
+
return FieldRenderer.renderArray(fieldName, config, value, context);
|
|
37
|
+
case 'message-editor':
|
|
38
|
+
return FieldRenderer.renderMessageEditor(fieldName, config, value, context);
|
|
39
|
+
default:
|
|
40
|
+
return html `<div>Unsupported field type: ${config.type}</div>`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
static renderTextInput(fieldName, config, value, context) {
|
|
44
|
+
const { errors = [], onChange, showLabel = true, extraClasses, style } = context;
|
|
45
|
+
// If field supports expression evaluation, use temba-completion
|
|
46
|
+
if (config.evaluated) {
|
|
47
|
+
return html `<temba-completion
|
|
48
|
+
name="${fieldName}"
|
|
49
|
+
label="${showLabel ? config.label : ''}"
|
|
50
|
+
?required="${config.required}"
|
|
51
|
+
.errors="${errors}"
|
|
52
|
+
.value="${value || ''}"
|
|
53
|
+
placeholder="${config.placeholder || ''}"
|
|
54
|
+
expressions="session"
|
|
55
|
+
.helpText="${config.helpText || ''}"
|
|
56
|
+
class="${extraClasses}"
|
|
57
|
+
style="${style}"
|
|
58
|
+
@input="${onChange || (() => { })}"
|
|
59
|
+
></temba-completion>`;
|
|
60
|
+
}
|
|
61
|
+
return html `<temba-textinput
|
|
62
|
+
name="${fieldName}"
|
|
63
|
+
label="${showLabel ? config.label : ''}"
|
|
64
|
+
?required="${config.required}"
|
|
65
|
+
.errors="${errors}"
|
|
66
|
+
.value="${value || ''}"
|
|
67
|
+
placeholder="${config.placeholder || ''}"
|
|
68
|
+
.helpText="${config.helpText || ''}"
|
|
69
|
+
class="${extraClasses}"
|
|
70
|
+
style="${style}"
|
|
71
|
+
@input="${onChange || (() => { })}"
|
|
72
|
+
></temba-textinput>`;
|
|
73
|
+
}
|
|
74
|
+
static renderTextarea(fieldName, config, value, context) {
|
|
75
|
+
const { errors = [], onChange, showLabel = true, extraClasses, style } = context;
|
|
76
|
+
const minHeightStyle = config.minHeight
|
|
77
|
+
? `--textarea-min-height: ${config.minHeight}px;`
|
|
78
|
+
: '';
|
|
79
|
+
const combinedStyle = `${minHeightStyle}${style}`;
|
|
80
|
+
// If field supports expression evaluation, use temba-completion
|
|
81
|
+
if (config.evaluated) {
|
|
82
|
+
return html `<temba-completion
|
|
83
|
+
name="${fieldName}"
|
|
84
|
+
label="${showLabel ? config.label : ''}"
|
|
85
|
+
?required="${config.required}"
|
|
86
|
+
.errors="${errors}"
|
|
87
|
+
.value="${value || ''}"
|
|
88
|
+
placeholder="${config.placeholder || ''}"
|
|
89
|
+
textarea
|
|
90
|
+
expressions="session"
|
|
91
|
+
.helpText="${config.helpText || ''}"
|
|
92
|
+
class="${extraClasses}"
|
|
93
|
+
style="${combinedStyle}"
|
|
94
|
+
@input="${onChange || (() => { })}"
|
|
95
|
+
></temba-completion>`;
|
|
96
|
+
}
|
|
97
|
+
return html `<temba-textinput
|
|
98
|
+
name="${fieldName}"
|
|
99
|
+
label="${showLabel ? config.label : ''}"
|
|
100
|
+
?required="${config.required}"
|
|
101
|
+
.errors="${errors}"
|
|
102
|
+
.value="${value || ''}"
|
|
103
|
+
placeholder="${config.placeholder || ''}"
|
|
104
|
+
textarea
|
|
105
|
+
.rows="${config.rows || 3}"
|
|
106
|
+
.helpText="${config.helpText || ''}"
|
|
107
|
+
class="${extraClasses}"
|
|
108
|
+
style="${combinedStyle}"
|
|
109
|
+
@input="${onChange || (() => { })}"
|
|
110
|
+
></temba-textinput>`;
|
|
111
|
+
}
|
|
112
|
+
static renderSelect(fieldName, config, value, context) {
|
|
113
|
+
var _a, _b;
|
|
114
|
+
const { errors = [], onChange, showLabel = true, flavor, extraClasses, style } = context;
|
|
115
|
+
// Ensure proper value handling for multi vs single select
|
|
116
|
+
const normalizedValue = (() => {
|
|
117
|
+
if (config.multi) {
|
|
118
|
+
// Multi-select: ensure we have an array and convert strings to option objects
|
|
119
|
+
const valueArray = Array.isArray(value) ? value : value ? [value] : [];
|
|
120
|
+
return valueArray.map((val) => {
|
|
121
|
+
if (typeof val === 'string') {
|
|
122
|
+
// Convert string values to option objects
|
|
123
|
+
return { name: val, value: val };
|
|
124
|
+
}
|
|
125
|
+
return val;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Single select: use the value as-is
|
|
130
|
+
return value || '';
|
|
131
|
+
}
|
|
132
|
+
})();
|
|
133
|
+
if (typeof normalizedValue === 'string') {
|
|
134
|
+
return html `<temba-select
|
|
135
|
+
name="${fieldName}"
|
|
136
|
+
?required="${config.required}"
|
|
137
|
+
.errors="${errors}"
|
|
138
|
+
value="${config.multi ? '' : normalizedValue}"
|
|
139
|
+
.values="${config.multi ? normalizedValue : undefined}"
|
|
140
|
+
?multi="${config.multi}"
|
|
141
|
+
?searchable="${config.searchable}"
|
|
142
|
+
?tags="${config.tags}"
|
|
143
|
+
?emails="${config.emails}"
|
|
144
|
+
?clearable="${config.clearable || false}"
|
|
145
|
+
label="${showLabel ? config.label : ''}"
|
|
146
|
+
placeholder="${config.placeholder || ''}"
|
|
147
|
+
maxItems="${config.maxItems || 0}"
|
|
148
|
+
valueKey="${config.valueKey || 'value'}"
|
|
149
|
+
nameKey="${config.nameKey || 'name'}"
|
|
150
|
+
endpoint="${config.endpoint || ''}"
|
|
151
|
+
.helpText="${config.helpText || ''}"
|
|
152
|
+
flavor="${flavor || config.flavor || 'small'}"
|
|
153
|
+
class="${extraClasses}"
|
|
154
|
+
style="${style}"
|
|
155
|
+
.getName=${config.getName}
|
|
156
|
+
.createArbitraryOption=${config.createArbitraryOption}
|
|
157
|
+
?allowCreate="${config.allowCreate || false}"
|
|
158
|
+
@change="${onChange || (() => { })}"
|
|
159
|
+
>
|
|
160
|
+
${(_a = config.options) === null || _a === void 0 ? void 0 : _a.map((option) => {
|
|
161
|
+
if (typeof option === 'string') {
|
|
162
|
+
return html `<temba-option
|
|
163
|
+
name="${option}"
|
|
164
|
+
value="${option}"
|
|
165
|
+
></temba-option>`;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
return html `<temba-option
|
|
169
|
+
name="${option.label || option.name}"
|
|
170
|
+
value="${option.value}"
|
|
171
|
+
></temba-option>`;
|
|
172
|
+
}
|
|
173
|
+
})}
|
|
174
|
+
</temba-select>`;
|
|
175
|
+
}
|
|
176
|
+
return html `<temba-select
|
|
177
|
+
name="${fieldName}"
|
|
178
|
+
label="${showLabel ? config.label : ''}"
|
|
179
|
+
?required="${config.required}"
|
|
180
|
+
.errors="${errors}"
|
|
181
|
+
.values="${normalizedValue}"
|
|
182
|
+
?multi="${config.multi}"
|
|
183
|
+
?searchable="${config.searchable}"
|
|
184
|
+
?tags="${config.tags}"
|
|
185
|
+
?emails="${config.emails}"
|
|
186
|
+
?clearable="${config.clearable || false}"
|
|
187
|
+
placeholder="${config.placeholder || ''}"
|
|
188
|
+
maxItems="${config.maxItems || 0}"
|
|
189
|
+
valueKey="${config.valueKey || 'value'}"
|
|
190
|
+
nameKey="${config.nameKey || 'name'}"
|
|
191
|
+
endpoint="${config.endpoint || ''}"
|
|
192
|
+
.helpText="${config.helpText || ''}"
|
|
193
|
+
flavor="${flavor || config.flavor || 'small'}"
|
|
194
|
+
class="${extraClasses}"
|
|
195
|
+
style="${style}"
|
|
196
|
+
.getName=${config.getName}
|
|
197
|
+
.createArbitraryOption=${config.createArbitraryOption}
|
|
198
|
+
?allowCreate="${config.allowCreate || false}"
|
|
199
|
+
@change="${onChange || (() => { })}"
|
|
200
|
+
>
|
|
201
|
+
${(_b = config.options) === null || _b === void 0 ? void 0 : _b.map((option) => {
|
|
202
|
+
if (typeof option === 'string') {
|
|
203
|
+
return html `<temba-option
|
|
204
|
+
name="${option}"
|
|
205
|
+
value="${option}"
|
|
206
|
+
></temba-option>`;
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
return html `<temba-option
|
|
210
|
+
name="${option.label || option.name}"
|
|
211
|
+
value="${option.value}"
|
|
212
|
+
></temba-option>`;
|
|
213
|
+
}
|
|
214
|
+
})}
|
|
215
|
+
</temba-select>`;
|
|
216
|
+
}
|
|
217
|
+
static renderCheckbox(fieldName, config, value, context) {
|
|
218
|
+
const { errors = [], onChange, extraClasses, style } = context;
|
|
219
|
+
return html `<div class="form-field">
|
|
220
|
+
<temba-checkbox
|
|
221
|
+
name="${fieldName}"
|
|
222
|
+
label="${config.label}"
|
|
223
|
+
.helpText="${config.helpText || ''}"
|
|
224
|
+
?required="${config.required}"
|
|
225
|
+
.errors="${errors}"
|
|
226
|
+
?checked="${value || false}"
|
|
227
|
+
size="${config.size || 1.2}"
|
|
228
|
+
animateChange="${config.animateChange || 'pulse'}"
|
|
229
|
+
class="${extraClasses}"
|
|
230
|
+
style="${style}"
|
|
231
|
+
@change="${onChange || (() => { })}"
|
|
232
|
+
></temba-checkbox>
|
|
233
|
+
${errors.length
|
|
234
|
+
? html `<div class="field-errors">${errors.join(', ')}</div>`
|
|
235
|
+
: ''}
|
|
236
|
+
</div>`;
|
|
237
|
+
}
|
|
238
|
+
static renderKeyValue(fieldName, config, value, context) {
|
|
239
|
+
const { errors = [], onChange, showLabel = true, extraClasses, style } = context;
|
|
240
|
+
return html `<div class="form-field">
|
|
241
|
+
${showLabel ? html `<label>${config.label}</label>` : ''}
|
|
242
|
+
<temba-key-value-editor
|
|
243
|
+
name="${fieldName}"
|
|
244
|
+
.value="${value || []}"
|
|
245
|
+
.sortable="${config.sortable}"
|
|
246
|
+
.keyPlaceholder="${config.keyPlaceholder || 'Key'}"
|
|
247
|
+
.valuePlaceholder="${config.valuePlaceholder || 'Value'}"
|
|
248
|
+
.minRows="${config.minRows || 0}"
|
|
249
|
+
class="${extraClasses}"
|
|
250
|
+
style="${style}"
|
|
251
|
+
@change="${onChange || (() => { })}"
|
|
252
|
+
></temba-key-value-editor>
|
|
253
|
+
${errors.length
|
|
254
|
+
? html `<div class="field-errors">${errors.join(', ')}</div>`
|
|
255
|
+
: ''}
|
|
256
|
+
</div>`;
|
|
257
|
+
}
|
|
258
|
+
static renderArray(fieldName, config, value, context) {
|
|
259
|
+
const { errors = [], onChange, showLabel = true, extraClasses, style } = context;
|
|
260
|
+
return html `<div class="form-field">
|
|
261
|
+
${showLabel ? html `<label>${config.label}</label>` : ''}
|
|
262
|
+
<temba-array-editor
|
|
263
|
+
.value="${value || []}"
|
|
264
|
+
.itemConfig="${config.itemConfig}"
|
|
265
|
+
.sortable="${config.sortable}"
|
|
266
|
+
.itemLabel="${config.itemLabel || 'Item'}"
|
|
267
|
+
.minItems="${config.minItems || 0}"
|
|
268
|
+
.maxItems="${config.maxItems || 0}"
|
|
269
|
+
.onItemChange="${config.onItemChange}"
|
|
270
|
+
.isEmptyItemFn="${config.isEmptyItem}"
|
|
271
|
+
class="${extraClasses}"
|
|
272
|
+
style="${style}"
|
|
273
|
+
@change="${onChange || (() => { })}"
|
|
274
|
+
></temba-array-editor>
|
|
275
|
+
${errors.length
|
|
276
|
+
? html `<div class="field-errors">${errors.join(', ')}</div>`
|
|
277
|
+
: ''}
|
|
278
|
+
</div>`;
|
|
279
|
+
}
|
|
280
|
+
static renderMessageEditor(fieldName, config, value, context) {
|
|
281
|
+
const { errors = [], onChange, showLabel = true, extraClasses, style, additionalData = {} } = context;
|
|
282
|
+
return html `<temba-message-editor
|
|
283
|
+
name="${fieldName}"
|
|
284
|
+
label="${showLabel ? config.label : ''}"
|
|
285
|
+
?required="${config.required}"
|
|
286
|
+
.errors="${errors}"
|
|
287
|
+
.value="${value || ''}"
|
|
288
|
+
.attachments="${additionalData.attachments || []}"
|
|
289
|
+
placeholder="${config.placeholder || ''}"
|
|
290
|
+
.helpText="${config.helpText || ''}"
|
|
291
|
+
?autogrow="${config.autogrow}"
|
|
292
|
+
?gsm="${config.gsm}"
|
|
293
|
+
?disableCompletion="${config.disableCompletion}"
|
|
294
|
+
counter="${config.counter || ''}"
|
|
295
|
+
accept="${config.accept || ''}"
|
|
296
|
+
endpoint="${config.endpoint || ''}"
|
|
297
|
+
max-attachments="${config.maxAttachments || 3}"
|
|
298
|
+
minHeight="${config.minHeight || 60}"
|
|
299
|
+
class="${extraClasses}"
|
|
300
|
+
style="${style}"
|
|
301
|
+
@change="${onChange || (() => { })}"
|
|
302
|
+
></temba-message-editor>`;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=FieldRenderer.js.map
|