@syncfusion/ej2-react-form-builder 0.1.0-beta
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/Common/CheckboxGroup.d.ts +13 -0
- package/Common/CheckboxGroup.js +50 -0
- package/Common/ConditionalRuleEngine.d.ts +296 -0
- package/Common/ConditionalRuleEngine.js +566 -0
- package/Common/CustomTabComponent.d.ts +17 -0
- package/Common/CustomTabComponent.js +30 -0
- package/Common/CustomValidationEngine.d.ts +58 -0
- package/Common/CustomValidationEngine.js +146 -0
- package/Common/DataGridComponent.d.ts +20 -0
- package/Common/DataGridComponent.js +273 -0
- package/Common/FormValidator.d.ts +485 -0
- package/Common/FormValidator.js +575 -0
- package/Common/MinMaxRuleEngine.d.ts +105 -0
- package/Common/MinMaxRuleEngine.js +365 -0
- package/Common/StaticHtmlRenderer.d.ts +14 -0
- package/Common/StaticHtmlRenderer.js +24 -0
- package/Common/expressions/DependencyTracker.d.ts +30 -0
- package/Common/expressions/DependencyTracker.js +128 -0
- package/Common/expressions/ExpressionEngine.d.ts +95 -0
- package/Common/expressions/ExpressionEngine.js +506 -0
- package/Common/expressions/ExpressionParser.d.ts +41 -0
- package/Common/expressions/ExpressionParser.js +269 -0
- package/Common/expressions/useGridExpressions.d.ts +12 -0
- package/Common/expressions/useGridExpressions.js +71 -0
- package/Common/globalFormId.d.ts +26 -0
- package/Common/globalFormId.js +45 -0
- package/Common/json-converter.d.ts +444 -0
- package/Common/json-converter.js +1184 -0
- package/Common/parser.d.ts +98 -0
- package/Common/parser.js +753 -0
- package/Common/utils.d.ts +130 -0
- package/Common/utils.js +574 -0
- package/README.md +53 -0
- package/index.d.ts +6 -0
- package/index.js +6 -0
- package/license +13 -0
- package/package.json +54 -0
- package/src/FormBuilder.d.ts +211 -0
- package/src/FormBuilder.js +1299 -0
- package/src/components/Database/ConnectionForm.d.ts +8 -0
- package/src/components/Database/ConnectionForm.js +28 -0
- package/src/components/TopBar.d.ts +10 -0
- package/src/components/TopBar.js +7 -0
- package/src/components/icons/ComponentIcons.d.ts +44 -0
- package/src/components/icons/ComponentIcons.js +40 -0
- package/src/components/layout/FormFieldWrapper.d.ts +41 -0
- package/src/components/layout/FormFieldWrapper.js +112 -0
- package/src/components/layout/LeftPane.d.ts +23 -0
- package/src/components/layout/LeftPane.js +310 -0
- package/src/components/layout/ListBoxPane.d.ts +24 -0
- package/src/components/layout/ListBoxPane.js +94 -0
- package/src/components/layout/MainSection.d.ts +39 -0
- package/src/components/layout/MainSection.js +1766 -0
- package/src/components/layout/Preview.d.ts +12 -0
- package/src/components/layout/Preview.js +337 -0
- package/src/components/layout/RightPane.d.ts +22 -0
- package/src/components/layout/RightPane.js +370 -0
- package/src/components/theme/ThemeSwitcher.d.ts +14 -0
- package/src/components/theme/ThemeSwitcher.js +24 -0
- package/src/components/theme/theme-constant.d.ts +37 -0
- package/src/components/theme/theme-constant.js +23 -0
- package/src/config/component-properties.json +4929 -0
- package/src/config/form-setting.json +56 -0
- package/src/config/locale.d.ts +449 -0
- package/src/config/locale.js +449 -0
- package/src/context/FormSchemaContext.d.ts +32 -0
- package/src/context/FormSchemaContext.js +155 -0
- package/src/context/LocalizationContext.d.ts +27 -0
- package/src/context/LocalizationContext.js +43 -0
- package/src/context/ThemeContext.d.ts +23 -0
- package/src/context/ThemeContext.js +161 -0
- package/src/context/contexts.d.ts +6 -0
- package/src/context/contexts.js +6 -0
- package/src/context/defaultFormSchema.d.ts +6 -0
- package/src/context/defaultFormSchema.js +20 -0
- package/src/defaultPalette.d.ts +9 -0
- package/src/defaultPalette.js +45 -0
- package/src/defaultTemplate.d.ts +3 -0
- package/src/defaultTemplate.js +185 -0
- package/src/import/schemaImportUtils.d.ts +24 -0
- package/src/import/schemaImportUtils.js +121 -0
- package/src/index.d.ts +16 -0
- package/src/index.js +6 -0
- package/src/types/ActionButtons.d.ts +44 -0
- package/src/types/ActionButtons.js +27 -0
- package/src/types/ComponentProperties.d.ts +33 -0
- package/src/types/ComponentProperties.js +2 -0
- package/src/types/FormSchema.d.ts +585 -0
- package/src/types/FormSchema.js +147 -0
- package/src/types/connection.d.ts +19 -0
- package/src/types/connection.js +9 -0
- package/src/utils/AttributesEditor.d.ts +13 -0
- package/src/utils/AttributesEditor.js +117 -0
- package/src/utils/ButtonGroupEditor.d.ts +15 -0
- package/src/utils/ButtonGroupEditor.js +141 -0
- package/src/utils/ChoiceBasedFieldEditor.d.ts +16 -0
- package/src/utils/ChoiceBasedFieldEditor.js +87 -0
- package/src/utils/ClipboardHelper.d.ts +26 -0
- package/src/utils/ClipboardHelper.js +83 -0
- package/src/utils/ColumnBuilder.d.ts +11 -0
- package/src/utils/ColumnBuilder.js +153 -0
- package/src/utils/ColumnSpanEditor.d.ts +8 -0
- package/src/utils/ColumnSpanEditor.js +98 -0
- package/src/utils/ConditionDataEditor.d.ts +29 -0
- package/src/utils/ConditionDataEditor.js +177 -0
- package/src/utils/ConditionalEditorUtils.d.ts +27 -0
- package/src/utils/ConditionalEditorUtils.js +310 -0
- package/src/utils/ConditionsEditor.d.ts +20 -0
- package/src/utils/ConditionsEditor.js +571 -0
- package/src/utils/CustomAccordion.d.ts +13 -0
- package/src/utils/CustomAccordion.js +38 -0
- package/src/utils/CustomValidationEditor.d.ts +11 -0
- package/src/utils/CustomValidationEditor.js +208 -0
- package/src/utils/DataSourceCollector.d.ts +3 -0
- package/src/utils/DataSourceCollector.js +252 -0
- package/src/utils/ExpressionEditor.d.ts +13 -0
- package/src/utils/ExpressionEditor.js +99 -0
- package/src/utils/FormSettingsPanel.d.ts +9 -0
- package/src/utils/FormSettingsPanel.js +60 -0
- package/src/utils/LookupConfig.d.ts +11 -0
- package/src/utils/LookupConfig.js +159 -0
- package/src/utils/MinMaxValueEditor.d.ts +25 -0
- package/src/utils/MinMaxValueEditor.js +188 -0
- package/src/utils/OptionsEditor.d.ts +8 -0
- package/src/utils/OptionsEditor.js +79 -0
- package/src/utils/PropertyRenderer.d.ts +22 -0
- package/src/utils/PropertyRenderer.js +210 -0
- package/src/utils/ValueRenderer.d.ts +2 -0
- package/src/utils/ValueRenderer.js +91 -0
- package/src/utils/ej2DbAdaptor.d.ts +6 -0
- package/src/utils/ej2DbAdaptor.js +38 -0
- package/src/utils/mappingHeuristics.d.ts +10 -0
- package/src/utils/mappingHeuristics.js +33 -0
- package/src/utils/renderComponent.d.ts +18 -0
- package/src/utils/renderComponent.js +300 -0
- package/styles/tailwind3.css +67 -0
- package/styles/themes/action-buttons.css +110 -0
- package/styles/themes/datagrid.css +380 -0
- package/styles/themes/drag-reorder.css +248 -0
- package/styles/themes/import-dialog.css +292 -0
- package/styles/themes/index.css +658 -0
- package/styles/themes/left-pane.css +253 -0
- package/styles/themes/options-editor.css +63 -0
- package/styles/themes/preview.css +702 -0
- package/styles/themes/property-dialog.css +371 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
3
|
+
import { TextBoxComponent } from '@syncfusion/ej2-react-inputs';
|
|
4
|
+
import { CustomValidationEngine } from '../../Common/CustomValidationEngine';
|
|
5
|
+
import { ButtonComponent, RadioButtonComponent } from '@syncfusion/ej2-react-buttons';
|
|
6
|
+
import { useLocalization } from '../context/LocalizationContext';
|
|
7
|
+
export const CustomValidationEditor = ({ componentFields, excludeFieldId, values, existingRules = [], onChange }) => {
|
|
8
|
+
const validationEngine = useRef(new CustomValidationEngine()).current;
|
|
9
|
+
const [errorMessage, setErrorMessage] = useState('Please enter a valid value.');
|
|
10
|
+
const [errorMessageError, setErrorMessageError] = useState('');
|
|
11
|
+
const [logicalOp, setLogicalOp] = useState('and');
|
|
12
|
+
const [rules, setRules] = useState([]);
|
|
13
|
+
const { l10n } = useLocalization();
|
|
14
|
+
// Build available field names
|
|
15
|
+
const availableFields = componentFields
|
|
16
|
+
.filter(c => c.name !== excludeFieldId)
|
|
17
|
+
.map(c => c.name);
|
|
18
|
+
// Initialize from existing rules - parse multiple conditions
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (existingRules.length > 0 && existingRules[0].expression) {
|
|
21
|
+
const parsed = validationEngine.parseExpression(existingRules[0].expression);
|
|
22
|
+
if (parsed) {
|
|
23
|
+
setErrorMessage(parsed.errorMessage);
|
|
24
|
+
// Detect logical operator (AND or OR) and split conditions
|
|
25
|
+
const condition = parsed.condition;
|
|
26
|
+
let detectedOp = 'and';
|
|
27
|
+
let splitConditions = [];
|
|
28
|
+
if (condition.includes(' || ')) {
|
|
29
|
+
detectedOp = 'or';
|
|
30
|
+
// Split by || but be careful with parentheses
|
|
31
|
+
splitConditions = splitConditionsByOperator(condition, '||');
|
|
32
|
+
}
|
|
33
|
+
else if (condition.includes(' && ')) {
|
|
34
|
+
detectedOp = 'and';
|
|
35
|
+
splitConditions = splitConditionsByOperator(condition, '&&');
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// Single condition
|
|
39
|
+
splitConditions = [condition];
|
|
40
|
+
}
|
|
41
|
+
setLogicalOp(detectedOp);
|
|
42
|
+
// Create rule objects for each condition
|
|
43
|
+
const newRules = splitConditions.map((cond, idx) => ({
|
|
44
|
+
id: (idx + 1).toString(),
|
|
45
|
+
condition: cond.trim(),
|
|
46
|
+
errors: []
|
|
47
|
+
}));
|
|
48
|
+
setRules(newRules.length > 0 ? newRules : [{ id: '1', condition: '', errors: [] }]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}, []);
|
|
52
|
+
// Helper to split conditions by operator while respecting parentheses
|
|
53
|
+
const splitConditionsByOperator = (expression, operator) => {
|
|
54
|
+
const conditions = [];
|
|
55
|
+
let current = '';
|
|
56
|
+
let depth = 0;
|
|
57
|
+
for (let i = 0; i < expression.length; i++) {
|
|
58
|
+
const char = expression[i];
|
|
59
|
+
if (char === '(')
|
|
60
|
+
depth++;
|
|
61
|
+
else if (char === ')')
|
|
62
|
+
depth--;
|
|
63
|
+
// Check for operator at depth 0
|
|
64
|
+
if (depth === 0 && expression.substr(i, operator.length) === operator) {
|
|
65
|
+
conditions.push(current.trim());
|
|
66
|
+
current = '';
|
|
67
|
+
i += operator.length - 1;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
current += char;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (current.trim()) {
|
|
74
|
+
conditions.push(current.trim());
|
|
75
|
+
}
|
|
76
|
+
return conditions.map(c => {
|
|
77
|
+
// Remove outer parentheses if present
|
|
78
|
+
c = c.trim();
|
|
79
|
+
if (c.startsWith('(') && c.endsWith(')')) {
|
|
80
|
+
c = c.slice(1, -1).trim();
|
|
81
|
+
}
|
|
82
|
+
return c;
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
const handleConditionChange = (id, condition) => {
|
|
86
|
+
const errors = validationEngine.validateConditionSyntax(condition, availableFields);
|
|
87
|
+
setRules(prev => {
|
|
88
|
+
const updated = prev.map(r => r.id === id ? { ...r, condition, errors } : r);
|
|
89
|
+
notifyParent(updated, errorMessage, logicalOp);
|
|
90
|
+
return updated;
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
const handleErrorMessageChange = (e) => {
|
|
94
|
+
const msg = e.value || '';
|
|
95
|
+
setErrorMessage(msg);
|
|
96
|
+
notifyParent(rules, msg, logicalOp);
|
|
97
|
+
};
|
|
98
|
+
const handleLogicalOpChange = (op) => {
|
|
99
|
+
setLogicalOp(op);
|
|
100
|
+
notifyParent(rules, errorMessage, op);
|
|
101
|
+
};
|
|
102
|
+
const addRule = () => {
|
|
103
|
+
const newId = Date.now().toString();
|
|
104
|
+
setRules(prev => [
|
|
105
|
+
...prev,
|
|
106
|
+
{ id: newId, condition: '', errors: [] }
|
|
107
|
+
]);
|
|
108
|
+
};
|
|
109
|
+
const removeRule = (id) => {
|
|
110
|
+
if (rules.length > 1) {
|
|
111
|
+
setRules(prev => {
|
|
112
|
+
const updated = prev.filter(r => r.id !== id);
|
|
113
|
+
notifyParent(updated, errorMessage, logicalOp);
|
|
114
|
+
return updated;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
clearAll();
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const clearAll = () => {
|
|
122
|
+
// Reset to initial state
|
|
123
|
+
setErrorMessage('');
|
|
124
|
+
setErrorMessageError('');
|
|
125
|
+
setLogicalOp('and');
|
|
126
|
+
setRules([]);
|
|
127
|
+
// Notify parent to remove from schema
|
|
128
|
+
onChange([]);
|
|
129
|
+
};
|
|
130
|
+
const notifyParent = (currentRules, msg, op) => {
|
|
131
|
+
const validConditions = currentRules
|
|
132
|
+
.filter(r => r.condition.trim() && r.errors.length === 0)
|
|
133
|
+
.map(r => {
|
|
134
|
+
let cond = r.condition.trim();
|
|
135
|
+
// Strip outer parentheses to normalize
|
|
136
|
+
while (cond.startsWith('(') && cond.endsWith(')')) {
|
|
137
|
+
const inner = cond.slice(1, -1).trim();
|
|
138
|
+
// Check if outer parens are actually matched
|
|
139
|
+
let depth = 0;
|
|
140
|
+
let isMatched = true;
|
|
141
|
+
for (let i = 0; i < inner.length; i++) {
|
|
142
|
+
if (inner[i] === '(')
|
|
143
|
+
depth++;
|
|
144
|
+
else if (inner[i] === ')')
|
|
145
|
+
depth--;
|
|
146
|
+
if (depth < 0) {
|
|
147
|
+
isMatched = false;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (isMatched && depth === 0) {
|
|
152
|
+
cond = inner;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return cond;
|
|
159
|
+
});
|
|
160
|
+
// Only notify if we have at least one valid condition and error message
|
|
161
|
+
if (validConditions.length > 0 && msg) {
|
|
162
|
+
// Combine conditions with && or ||
|
|
163
|
+
const operator = op === 'and' ? ' && ' : ' || ';
|
|
164
|
+
// Wrap each condition in parentheses (only one layer)
|
|
165
|
+
const wrappedConditions = validConditions.map(cond => `(${cond})`);
|
|
166
|
+
const combinedCondition = wrappedConditions.join(` ${operator} `);
|
|
167
|
+
const fullExpression = validationEngine.buildExpression(combinedCondition, msg);
|
|
168
|
+
onChange([{
|
|
169
|
+
condition: undefined,
|
|
170
|
+
expression: fullExpression
|
|
171
|
+
}]);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
return (_jsxs("div", { className: "custom-validation-editor", role: "group", "aria-label": l10n.getConstant(values.label), children: [_jsxs("div", { style: { display: 'flex', justifyContent: "space-between" }, children: [_jsx("label", { className: "property-label", style: { fontWeight: '600', marginBottom: "0.5rem", fontSize: '0.875rem' }, children: l10n.getConstant(values.label) }), _jsx("div", { style: { marginBottom: "0.5rem" }, children: _jsx(ButtonComponent, { onClick: addRule, iconCss: 'e-icons e-plus', cssClass: "e-flat", title: "Add Condition", "aria-label": l10n.getConstant('addHeader'), children: l10n.getConstant('addHeader') }) })] }), _jsx("div", { className: "custom-validation-description", style: { marginBottom: "0.75rem" }, children: _jsx("p", { className: "property-description", children: l10n.getConstant('customValidationDescription') }) }), _jsxs("div", { className: "e-group-container", "aria-labelledby": "custom-validation-heading", "aria-describedby": "custom-validation-description", children: [rules.length > 1 && (_jsx("div", { className: "e-group-header", children: _jsxs("div", { className: "e-logical-op-radio-group", role: "radiogroup", "aria-label": "Logical Operator", style: { display: 'flex', flexDirection: 'column', gap: '1.5rem' }, children: [_jsx(RadioButtonComponent, { id: "qb-and-rules", name: "qb-logical-op", value: "and", label: "AND", checked: logicalOp === 'and', labelPosition: "After", change: (e) => {
|
|
175
|
+
if (e)
|
|
176
|
+
handleLogicalOpChange(e.value);
|
|
177
|
+
} }), _jsx(RadioButtonComponent, { id: "qb-or-rules", name: "qb-logical-op", value: "or", label: "OR", checked: logicalOp === 'or', labelPosition: "After", change: (e) => {
|
|
178
|
+
if (e)
|
|
179
|
+
handleLogicalOpChange(e.value);
|
|
180
|
+
} })] }) })), _jsx("div", { className: "e-group-body", children: _jsx("div", { className: "e-rule-list", children: rules.map((rule, idx) => (_jsxs(React.Fragment, { children: [idx > 0 && (_jsxs("div", { "aria-hidden": "true", style: {
|
|
181
|
+
display: 'flex',
|
|
182
|
+
alignItems: 'center',
|
|
183
|
+
gap: '8px',
|
|
184
|
+
margin: '8px 0'
|
|
185
|
+
}, children: [_jsx("div", { style: { flex: 1, height: '1px', background: 'rgb(var(--sf-color-line-color))' } }), _jsx("span", { style: {
|
|
186
|
+
border: '1px solid rgb(var(--sf-color-line-color))',
|
|
187
|
+
borderRadius: '9999px',
|
|
188
|
+
padding: '2px 5px',
|
|
189
|
+
fontSize: '10px',
|
|
190
|
+
lineHeight: 1.5,
|
|
191
|
+
}, children: logicalOp === 'and' ? 'AND' : 'OR' }), _jsx("div", { style: { flex: 1, height: '1px', background: 'rgb(var(--sf-color-line-color))' } })] })), _jsxs("div", { className: "e-rule-container e-horizontal-mode", role: "group", "aria-label": `Condition ${idx + 1}`, children: [_jsxs("div", { className: "e-rule-field", children: [_jsx("div", { className: "e-rule-value", children: _jsx("input", { value: rule.condition, onChange: (e) => handleConditionChange(rule.id, e.target.value), placeholder: "(input === {fieldName})", className: "e-input custom-validation-textarea", "aria-label": `Condition ${idx + 1}`, "aria-invalid": rule.errors.length > 0, "aria-describedby": rule.errors.length > 0
|
|
192
|
+
? `condition-errors-${rule.id}`
|
|
193
|
+
: undefined }) }), _jsx("div", { children: _jsx(ButtonComponent, { iconCss: 'e-icons e-trash', onClick: () => removeRule(rule.id), title: "Remove this condition", cssClass: "e-small e-flat", "aria-label": `Remove condition ${idx + 1}` }) })] }), rule.errors.length > 0 && (_jsx("div", { className: "custom-validation-field-errors", role: "alert", "aria-live": "polite", children: rule.errors.map((error, errIdx) => (_jsx("div", { className: "e-error", children: error }, errIdx))) }))] })] }, rule.id))) }) })] }), _jsxs("div", { className: "custom-validation-error-message form-group", style: { marginTop: '0.75rem' }, children: [_jsxs("label", { style: { fontWeight: 400, fontSize: '14px' }, children: [l10n.getConstant('validationMessage'), " ", _jsx("span", { style: { color: '#e74c3c' }, children: "*" })] }), _jsx(TextBoxComponent, { placeholder: l10n.getConstant('provideCustomValidationMessage'), value: errorMessage, change: handleErrorMessageChange, blur: (e) => {
|
|
194
|
+
if (e.value === '') {
|
|
195
|
+
setErrorMessageError(l10n.getConstant('customValidationErrorMessage'));
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
setErrorMessageError('');
|
|
199
|
+
}
|
|
200
|
+
}, floatLabelType: "Never", cssClass: `custom-validation-error-input ${errorMessageError ? 'e-error' : ''}`, htmlAttributes: {
|
|
201
|
+
'aria-labelledby': 'validation-message-label',
|
|
202
|
+
'aria-required': 'true',
|
|
203
|
+
'aria-invalid': errorMessageError ? 'true' : 'false',
|
|
204
|
+
...(errorMessageError && {
|
|
205
|
+
'aria-describedby': 'validation-message-error'
|
|
206
|
+
})
|
|
207
|
+
} }), errorMessageError && (_jsx("div", { className: "e-error", style: { marginTop: '6px' }, role: "alert", "aria-live": "polite", children: errorMessageError }))] })] }));
|
|
208
|
+
};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
3
|
+
import { TextBoxComponent } from '@syncfusion/ej2-react-inputs';
|
|
4
|
+
import { DropDownListComponent } from '@syncfusion/ej2-react-dropdowns';
|
|
5
|
+
import { ButtonComponent, RadioButtonComponent, SwitchComponent } from '@syncfusion/ej2-react-buttons';
|
|
6
|
+
import { useLocalization } from '../context/LocalizationContext';
|
|
7
|
+
import { OptionsEditor } from './OptionsEditor';
|
|
8
|
+
import { AccordionWrapper } from './CustomAccordion';
|
|
9
|
+
export const DataSourceCollector = ({ value, onChange, type = 'options', datakey, isAccordion = false }) => {
|
|
10
|
+
const { l10n } = useLocalization();
|
|
11
|
+
const isGridType = type === 'grid';
|
|
12
|
+
const defaultType = isGridType ? 'raw_data' : 'options';
|
|
13
|
+
const [dataSourceType, setDataSourceType] = useState(defaultType);
|
|
14
|
+
const [rawJsonData, setRawJsonData] = useState(JSON.stringify(value));
|
|
15
|
+
const [optionData, setOptionData] = useState(null);
|
|
16
|
+
const [formNames, setFormNames] = useState([]);
|
|
17
|
+
const [selectedFormName, setSelectedFormName] = useState(typeof value === 'object' && (value?.formName) ? value?.formName : '');
|
|
18
|
+
const [isLoadingFormNames, setIsLoadingFormNames] = useState(true);
|
|
19
|
+
const [lookupErrorMessage, setLookupErrorMessage] = useState('');
|
|
20
|
+
const [remoteUrl, setRemoteUrl] = useState('');
|
|
21
|
+
const [headers, setHeaders] = useState([]);
|
|
22
|
+
const [dataPath, setDataPath] = useState('');
|
|
23
|
+
const [search, setSearch] = useState({ enabled: false, fields: [] });
|
|
24
|
+
const [sort, setSort] = useState({ enabled: false, field: '', direction: 'ascending' });
|
|
25
|
+
const [filters, setFilters] = useState([]);
|
|
26
|
+
const [adaptorType, setAdaptorType] = useState('WebApiAdaptor');
|
|
27
|
+
const [take, setTake] = useState(null);
|
|
28
|
+
const [skip, setSkip] = useState(null);
|
|
29
|
+
const [errorMessage, setErrorMessage] = useState('');
|
|
30
|
+
const [successMessage, setSuccessMessage] = useState('');
|
|
31
|
+
// Initialize with current value
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
34
|
+
setDataSourceType(value.type === 'url' ? 'remote_url' : value.type === 'raw' ? 'raw_data' : (isGridType ? 'raw_data' : 'options'));
|
|
35
|
+
if (value.type === 'lookup' && value.formName) {
|
|
36
|
+
setDataSourceType('lookup');
|
|
37
|
+
setSelectedFormName(value.formName);
|
|
38
|
+
}
|
|
39
|
+
if (value.url)
|
|
40
|
+
setRemoteUrl(value.url);
|
|
41
|
+
if (value.headers)
|
|
42
|
+
setHeaders(value.headers);
|
|
43
|
+
if (value.search)
|
|
44
|
+
setSearch(value.search);
|
|
45
|
+
if (value.sort)
|
|
46
|
+
setSort(value.sort);
|
|
47
|
+
if (value.filters)
|
|
48
|
+
setFilters(value.filters);
|
|
49
|
+
if (value.adaptorType)
|
|
50
|
+
setAdaptorType(value.adaptorType);
|
|
51
|
+
if (value.take)
|
|
52
|
+
setTake(value.take);
|
|
53
|
+
if (value.skip)
|
|
54
|
+
setSkip(value.skip);
|
|
55
|
+
if (value.dataPath)
|
|
56
|
+
setDataPath(value.dataPath);
|
|
57
|
+
}
|
|
58
|
+
else if (Array.isArray(value)) {
|
|
59
|
+
setRawJsonData(JSON.stringify(value));
|
|
60
|
+
}
|
|
61
|
+
}, []);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const fetchFormNames = async () => {
|
|
64
|
+
try {
|
|
65
|
+
const response = await fetch(/*formNameUrl*/ '');
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
throw new Error(`Network response was not ok: ${response.status} ${response.statusText}`);
|
|
68
|
+
}
|
|
69
|
+
const data = await response.json();
|
|
70
|
+
const names = Array.isArray(data) ? data.map((item) => item.name).filter(Boolean) : [];
|
|
71
|
+
setFormNames(names);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error('Error fetching form names:', error);
|
|
75
|
+
setFormNames([]);
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
setIsLoadingFormNames(false);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
if (isGridType) {
|
|
82
|
+
fetchFormNames();
|
|
83
|
+
}
|
|
84
|
+
}, [isGridType]);
|
|
85
|
+
// Filter options based on type
|
|
86
|
+
const allDataSourceOptions = [
|
|
87
|
+
{ text: l10n.getConstant('options'), value: 'options' },
|
|
88
|
+
{ text: l10n.getConstant('rawData') || 'Raw Data', value: 'raw_data' },
|
|
89
|
+
{ text: l10n.getConstant('remoteUrl') || 'Remote URL', value: 'remote_url' }
|
|
90
|
+
];
|
|
91
|
+
const dataSourceOptions = isGridType
|
|
92
|
+
? allDataSourceOptions.filter(opt => opt.value !== 'options') /*.concat([{ text: l10n.getConstant('lookup') || 'Lookup', value: 'lookup' }])*/
|
|
93
|
+
: allDataSourceOptions;
|
|
94
|
+
const handleDataSourceTypeChange = (e) => {
|
|
95
|
+
setDataSourceType(e.value);
|
|
96
|
+
setErrorMessage('');
|
|
97
|
+
setSuccessMessage('');
|
|
98
|
+
setHeaders([]);
|
|
99
|
+
if (e.value !== 'lookup') {
|
|
100
|
+
setSelectedFormName('');
|
|
101
|
+
setLookupErrorMessage('');
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const handleHeaderKeyChange = (index, newKey) => {
|
|
105
|
+
const updatedHeaders = [...headers];
|
|
106
|
+
updatedHeaders[index].key = newKey;
|
|
107
|
+
setHeaders(updatedHeaders);
|
|
108
|
+
};
|
|
109
|
+
const handleHeaderValueChange = (index, newValue) => {
|
|
110
|
+
const updatedHeaders = [...headers];
|
|
111
|
+
updatedHeaders[index].value = newValue;
|
|
112
|
+
setHeaders(updatedHeaders);
|
|
113
|
+
};
|
|
114
|
+
const handleAddHeader = () => {
|
|
115
|
+
setHeaders([...headers, { key: '', value: '' }]);
|
|
116
|
+
};
|
|
117
|
+
const handleRemoveHeader = (index) => {
|
|
118
|
+
const updatedHeaders = headers.filter((_, i) => i !== index);
|
|
119
|
+
setHeaders(updatedHeaders);
|
|
120
|
+
};
|
|
121
|
+
const handleRawDataChange = (e) => {
|
|
122
|
+
setRawJsonData(e.value);
|
|
123
|
+
setErrorMessage('');
|
|
124
|
+
try {
|
|
125
|
+
const parsed = JSON.parse(e.value);
|
|
126
|
+
if (Array.isArray(parsed)) {
|
|
127
|
+
onChange(parsed);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
const errorMsg = err instanceof Error ? err.message : 'Invalid JSON format';
|
|
132
|
+
setErrorMessage(l10n.getConstant('invalidJson') || `Invalid JSON: ${errorMsg}`);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const handleOptionDataChange = (e) => {
|
|
136
|
+
onChange(e);
|
|
137
|
+
setOptionData(e);
|
|
138
|
+
};
|
|
139
|
+
const handleSaveUrl = () => {
|
|
140
|
+
if (!remoteUrl.trim()) {
|
|
141
|
+
setErrorMessage(l10n.getConstant('pleaseEnterUrl') || 'Please enter a URL');
|
|
142
|
+
setSuccessMessage('');
|
|
143
|
+
onChange([]);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// Save URL and query parameters
|
|
147
|
+
const config = {
|
|
148
|
+
type: 'url',
|
|
149
|
+
url: remoteUrl,
|
|
150
|
+
headers: headers.filter(h => h.key.trim()),
|
|
151
|
+
adaptorType: adaptorType,
|
|
152
|
+
dataPath: dataPath || undefined, // Add this line
|
|
153
|
+
search: search?.enabled ? search : undefined,
|
|
154
|
+
sort: sort?.enabled ? sort : undefined,
|
|
155
|
+
filters: filters?.length > 0 ? filters : undefined,
|
|
156
|
+
take: take != null && take > 0 ? take : undefined,
|
|
157
|
+
skip: skip != null && skip > 0 ? skip : undefined
|
|
158
|
+
};
|
|
159
|
+
onChange(config);
|
|
160
|
+
setErrorMessage('');
|
|
161
|
+
setSuccessMessage(l10n.getConstant('urlSavedSuccessfully') || 'URL configuration saved successfully');
|
|
162
|
+
};
|
|
163
|
+
const handleLookupFormNameChange = useCallback((args) => {
|
|
164
|
+
const formName = args.value;
|
|
165
|
+
setSelectedFormName(formName);
|
|
166
|
+
setLookupErrorMessage('');
|
|
167
|
+
if (formName) {
|
|
168
|
+
onChange({
|
|
169
|
+
type: 'lookup',
|
|
170
|
+
formName: formName
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
onChange([]);
|
|
175
|
+
}
|
|
176
|
+
}, [onChange]);
|
|
177
|
+
const content = (_jsxs("div", { className: "data-source-collector", children: [_jsxs("div", { className: "data-source-type-selector", role: "radiogroup", "aria-label": l10n.getConstant('dataSource'), style: { marginBottom: "1rem", display: 'flex', flexDirection: 'column', gap: '1rem' }, children: [dataSourceOptions.map((option) => (_jsx(RadioButtonComponent, { id: `ds-${option.value}-${datakey}`, name: `dataSourceType-${datakey}`, value: option.value, label: option.text, checked: dataSourceType === option.value, labelPosition: "After", change: (e) => {
|
|
178
|
+
if (e) {
|
|
179
|
+
handleDataSourceTypeChange({ value: e.value });
|
|
180
|
+
}
|
|
181
|
+
} }, `${option.value}`))), _jsx("div", { style: { height: '1px', background: 'rgb(var(--sf-color-line-color))' } })] }, datakey), _jsxs("div", { className: "data-source-content", children: [dataSourceType === 'options' && !isGridType && (_jsx(OptionsEditor, { options: optionData !== null ? optionData : Array.isArray(value) ? value : (value?.data || []), onChange: handleOptionDataChange, type: type })), dataSourceType === 'raw_data' && (_jsxs("div", { className: "raw-data-editor", children: [_jsxs("label", { className: 'property-label', style: { fontWeight: 500, marginBottom: '0.75rem' }, children: [l10n.getConstant('json'), " ", l10n.getConstant('data')] }), _jsx(TextBoxComponent, { value: rawJsonData, placeholder: l10n.getConstant('pasteJsonArrayRecords') || 'e.g., ["Option 1", "Option 2"]', multiline: true, change: handleRawDataChange, floatLabelType: "Never", htmlAttributes: {
|
|
182
|
+
'aria-labelledby': `${datakey}-json-data-label`
|
|
183
|
+
} })] })), dataSourceType === 'remote_url' && (_jsxs("div", { className: "remote-url-editor", children: [_jsxs("div", { className: "url-input-group", style: { marginBottom: "1rem" }, children: [_jsx("label", { className: 'property-label', style: { fontWeight: 500, marginBottom: '0.5rem' }, children: l10n.getConstant('endpointUrl') }), _jsx(TextBoxComponent, { value: remoteUrl, placeholder: l10n.getConstant('enterUrl'), change: (e) => {
|
|
184
|
+
setRemoteUrl(e.value);
|
|
185
|
+
setErrorMessage('');
|
|
186
|
+
}, floatLabelType: "Never", htmlAttributes: {
|
|
187
|
+
'aria-labelledby': `${datakey}-endpoint-url-label`
|
|
188
|
+
} })] }), _jsxs("div", { className: "data-path-input", style: { marginBottom: "1rem" }, children: [_jsx("div", { className: 'property-label', style: { fontWeight: 500, marginBottom: "0.5rem" }, children: l10n.getConstant('dataPath') }), _jsx(TextBoxComponent, { value: dataPath, placeholder: l10n.getConstant('dataPathPlaceholder'), change: (e) => setDataPath(e.value), floatLabelType: "Never", htmlAttributes: {
|
|
189
|
+
'aria-labelledby': `${datakey}-data-path-label`
|
|
190
|
+
} })] }), _jsxs("div", { className: "headers-editor", style: { marginBottom: "0.25rem" }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: "space-between" }, children: [_jsx("div", { className: 'property-label', style: { fontWeight: 500, marginBottom: "0.75rem" }, children: l10n.getConstant('headers') }), _jsx(ButtonComponent, { style: { marginBottom: "0.75rem" }, onClick: handleAddHeader, iconCss: 'e-icons e-plus', cssClass: "e-flat", title: "Add Condition", "aria-label": l10n.getConstant('addHeader'), children: l10n.getConstant('addHeader') })] }), headers.map((header, index) => (_jsxs("div", { style: { display: "flex", gap: "0.5rem", marginBottom: "0.75rem" }, children: [_jsx(TextBoxComponent, { value: header.key, placeholder: l10n.getConstant('headerKey'), change: (e) => handleHeaderKeyChange(index, e.value), floatLabelType: "Never", style: { flex: 1 }, width: '90px', htmlAttributes: {
|
|
191
|
+
'aria-label': `${l10n.getConstant('headerKey')} ${index + 1}`
|
|
192
|
+
} }), _jsx(TextBoxComponent, { value: header.value, placeholder: "Value", change: (e) => handleHeaderValueChange(index, e.value), floatLabelType: "Never", style: { flex: 1 }, width: '90px', htmlAttributes: {
|
|
193
|
+
'aria-label': `${l10n.getConstant('value')} ${index + 1}`
|
|
194
|
+
} }), _jsx(ButtonComponent, { onClick: () => handleRemoveHeader(index), cssClass: 'e-flat', iconCss: 'e-icons e-trash', type: "button", "aria-label": `${l10n.getConstant('delete')} ${l10n.getConstant('header')} ${index + 1}` })] }, index)))] }), _jsxs("div", { className: "adaptor-selector", style: { marginBottom: "0.75rem" }, children: [_jsx("div", { className: 'property-label', style: { fontWeight: 500, marginBottom: "0.5rem" }, children: l10n.getConstant('adaptorType') }), _jsx("div", { style: { marginBottom: "1rem" }, children: _jsx(DropDownListComponent, { dataSource: [
|
|
195
|
+
{ text: 'Web API Adaptor', value: 'WebApiAdaptor' },
|
|
196
|
+
{ text: 'URL Adaptor', value: 'UrlAdaptor' },
|
|
197
|
+
{ text: 'OData V4 Adaptor', value: 'ODataV4Adaptor' }
|
|
198
|
+
], fields: { text: 'text', value: 'value' }, value: adaptorType, change: (e) => setAdaptorType(e.value), floatLabelType: "Never", htmlAttributes: {
|
|
199
|
+
'aria-labelledby': `${datakey}-adaptor-type-label`
|
|
200
|
+
} }) }), _jsx("div", { style: { height: '0.5px', background: 'rgb(var(--sf-color-line-color))' } })] }), _jsxs(_Fragment, { children: [_jsx("div", { className: 'property-label', style: { fontWeight: 600, marginBottom: "0.5rem" }, children: l10n.getConstant('queryConfiguration') }), _jsxs("div", { className: 'query-section', children: [_jsxs("div", { style: { display: 'flex', justifyContent: "space-between" }, children: [_jsx("label", { className: 'property-label', style: { fontWeight: 600, marginBottom: "0.5rem" }, children: l10n.getConstant('search') }), _jsx("div", { children: _jsx(SwitchComponent, { checked: search.enabled, change: (e) => setSearch({ ...search, enabled: e.checked }), htmlAttributes: {
|
|
201
|
+
'aria-label': l10n.getConstant('search')
|
|
202
|
+
} }) })] }), search.enabled && (_jsxs(_Fragment, { children: [_jsxs("div", { style: { marginBottom: "0.75rem" }, children: [_jsx("div", { className: 'property-label', style: { fontWeight: 500, marginBottom: "0.5rem" }, children: l10n.getConstant('queryParameterName') }), _jsx("div", { className: 'property-description', children: l10n.getConstant('fieldToSearch') }), _jsx(TextBoxComponent, { value: search.key || `${l10n.getConstant('search')}`, change: (e) => setSearch({ ...search, key: e.value }), floatLabelType: "Always", htmlAttributes: {
|
|
203
|
+
'aria-label': l10n.getConstant('queryParameterName')
|
|
204
|
+
} })] }), _jsxs("div", { children: [_jsx("div", { className: 'property-label', style: { fontWeight: 500, marginBottom: "0.5rem" }, children: "Fields" }), _jsx("div", { className: 'property-description', children: l10n.getConstant('fieldsToSearchIn') }), _jsx(TextBoxComponent, { value: search.fields.join(', '), change: (e) => setSearch({ ...search, fields: e.value.split(',').map((f) => f.trim()).filter((f) => f) }), floatLabelType: "Always", multiline: true, htmlAttributes: {
|
|
205
|
+
'aria-label': l10n.getConstant('fieldsToSearchIn')
|
|
206
|
+
} })] })] }))] }), _jsxs("div", { className: "query-section", children: [_jsxs("div", { style: { display: 'flex', justifyContent: "space-between" }, children: [_jsx("label", { className: 'property-label', style: { fontWeight: 600, marginBottom: "0.5rem" }, children: l10n.getConstant('sort') }), _jsx("div", { children: _jsx(SwitchComponent, { checked: sort.enabled, change: (e) => setSort({ ...sort, enabled: e.checked }), htmlAttributes: {
|
|
207
|
+
'aria-label': l10n.getConstant('sort')
|
|
208
|
+
} }) })] }), sort.enabled && (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [_jsx("div", { className: 'property-label', style: { fontWeight: 500 }, children: "Field" }), _jsx(TextBoxComponent, { value: sort.field, placeholder: l10n.getConstant('fieldToSortBy'), change: (e) => setSort({ ...sort, field: e.value }), floatLabelType: "Never", style: { flex: 1 }, htmlAttributes: {
|
|
209
|
+
'aria-label': l10n.getConstant('fieldToSortBy')
|
|
210
|
+
} }), _jsx("div", { className: 'property-label', style: { fontWeight: 500 }, children: "Direction" }), _jsx(DropDownListComponent, { dataSource: [
|
|
211
|
+
{ text: l10n.getConstant('ascending'), value: 'ascending' },
|
|
212
|
+
{ text: l10n.getConstant('descending'), value: 'descending' }
|
|
213
|
+
], fields: { text: 'text', value: 'value' }, value: sort.direction, change: (e) => setSort({ ...sort, direction: e.value }), floatLabelType: "Never", style: { flex: 1 }, htmlAttributes: {
|
|
214
|
+
'aria-label': 'Direction'
|
|
215
|
+
} })] }))] }), _jsxs("div", { className: "query-section", children: [_jsxs("div", { style: { display: 'flex', justifyContent: "space-between" }, children: [_jsx("div", { className: 'property-label', style: { fontWeight: 500, marginBottom: "0.25rem" }, children: l10n.getConstant('filters') }), _jsx(ButtonComponent, { style: { marginBottom: "0.25rem" }, onClick: () => setFilters([...filters, { field: '', operator: 'equal', value: '' }]), iconCss: 'e-icons e-plus', cssClass: "e-flat", title: "Add Condition", "aria-label": l10n.getConstant('addHeader'), children: l10n.getConstant('addHeader') })] }), filters.length > 0 &&
|
|
216
|
+
_jsx("div", { className: 'property-description', style: { marginBottom: "0.75rem" }, children: l10n.getConstant('fieldToSearch') }), filters.map((filter, index) => (_jsxs("div", { style: { display: "flex", gap: "0.5rem", marginBottom: "0.5rem", flexWrap: 'wrap', padding: '6px', border: '1px solid rgb(var(--sf-color-line-color))', borderRadius: '12px' }, children: [_jsx(TextBoxComponent, { value: filter.field, width: "185px", placeholder: l10n.getConstant('fieldStatus'), change: (e) => {
|
|
217
|
+
const updated = [...filters];
|
|
218
|
+
updated[index].field = e.value;
|
|
219
|
+
setFilters(updated);
|
|
220
|
+
}, floatLabelType: "Never", style: { flex: 1 }, htmlAttributes: {
|
|
221
|
+
'aria-label': `${l10n.getConstant('fieldStatus')} ${index + 1}`
|
|
222
|
+
} }), datakey === 'data' &&
|
|
223
|
+
_jsx(ButtonComponent, { cssClass: "e-flat", iconCss: 'e-icons e-trash', onClick: () => setFilters(filters.filter((_, i) => i !== index)), type: "button", "aria-label": `${l10n.getConstant('delete')} ${l10n.getConstant('filters')} ${index + 1}` }), _jsx(DropDownListComponent, { dataSource: [
|
|
224
|
+
{ text: '=', value: 'equal' },
|
|
225
|
+
{ text: '!=', value: 'notequal' },
|
|
226
|
+
{ text: 'contains', value: 'contains' },
|
|
227
|
+
{ text: '>', value: 'greaterthan' },
|
|
228
|
+
{ text: '<', value: 'lessthan' },
|
|
229
|
+
{ text: '>=', value: 'greaterthanorequal' },
|
|
230
|
+
{ text: '<=', value: 'lessthanorequal' }
|
|
231
|
+
], fields: { text: 'text', value: 'value' }, value: filter.operator, width: "105px", popupWidth: "100px", change: (e) => {
|
|
232
|
+
const updated = [...filters];
|
|
233
|
+
updated[index].operator = e.value;
|
|
234
|
+
setFilters(updated);
|
|
235
|
+
}, floatLabelType: "Never", style: { flex: 1 }, htmlAttributes: {
|
|
236
|
+
'aria-label': `Operator ${index + 1}`
|
|
237
|
+
} }), _jsx(TextBoxComponent, { value: filter.value, placeholder: l10n.getConstant('value'), width: "105px", change: (e) => {
|
|
238
|
+
const updated = [...filters];
|
|
239
|
+
updated[index].value = e.value;
|
|
240
|
+
setFilters(updated);
|
|
241
|
+
}, floatLabelType: "Never", style: { flex: 1 }, htmlAttributes: {
|
|
242
|
+
'aria-label': `${l10n.getConstant('value')} ${index + 1}`
|
|
243
|
+
} }), datakey === 'Conditionaldata' &&
|
|
244
|
+
_jsx(ButtonComponent, { cssClass: "e-flat", iconCss: 'e-icons e-trash', onClick: () => setFilters(filters.filter((_, i) => i !== index)), type: "button", "aria-label": `${l10n.getConstant('delete')} ${l10n.getConstant('filters')} ${index + 1}` })] }, index)))] }), _jsx("div", { className: "query-section", children: _jsxs("div", { style: { display: "flex", gap: "0.5rem" }, children: [_jsxs("div", { children: [_jsx("div", { className: 'property-label', style: { fontWeight: 500, marginBottom: "0.25rem" }, children: l10n.getConstant('offset') }), _jsx(TextBoxComponent, { value: skip !== null ? skip.toString() : '', placeholder: l10n.getConstant('skipOffset'), change: (e) => setSkip(parseInt(e.value) || 0), floatLabelType: "Never", type: "number", style: { flex: 1 }, htmlAttributes: {
|
|
245
|
+
'aria-label': l10n.getConstant('offset')
|
|
246
|
+
} })] }), _jsxs("div", { children: [_jsx("div", { className: 'property-label', style: { fontWeight: 500, marginBottom: "0.25rem" }, children: l10n.getConstant('limit') }), _jsx(TextBoxComponent, { value: take !== null ? take.toString() : '', placeholder: l10n.getConstant('takeLimit'), change: (e) => setTake(parseInt(e.value) || 0), floatLabelType: "Never", type: "number", style: { flex: 1 }, htmlAttributes: {
|
|
247
|
+
'aria-label': l10n.getConstant('limit')
|
|
248
|
+
} })] })] }) })] }), _jsx(ButtonComponent, { style: { marginTop: '0.75rem' }, cssClass: "e-primary", onClick: handleSaveUrl, type: "button", "aria-label": l10n.getConstant('save'), children: l10n.getConstant('save') })] })), dataSourceType === 'lookup' && (_jsxs("div", { className: "lookup-editor", style: { marginBottom: '0.75rem' }, children: [_jsx("div", { className: 'property-description', style: { marginBottom: '0.5rem' }, children: l10n.getConstant('selectFormSubmissions') }), _jsxs("div", { style: { marginBottom: '0.5rem' }, children: [_jsx("label", { style: { display: 'block', marginBottom: '0.5rem', fontWeight: 500 }, children: l10n.getConstant('selectFormName') }), _jsx(DropDownListComponent, { dataSource: formNames, value: selectedFormName, change: handleLookupFormNameChange, placeholder: l10n.getConstant('selectFormName'), popupHeight: "200px", allowFiltering: true, enabled: !isLoadingFormNames, floatLabelType: "Never", htmlAttributes: {
|
|
249
|
+
'aria-labelledby': `${datakey}-form-name-label`
|
|
250
|
+
} })] }), lookupErrorMessage && (_jsx("p", { className: "e-error", style: { marginTop: '0.5rem' }, role: "alert", "aria-live": "assertive", children: lookupErrorMessage }))] })), errorMessage && (_jsx("p", { className: "e-error", role: "alert", "aria-live": "assertive", children: errorMessage })), successMessage && (_jsx("p", { className: "e-success", role: "alert", "aria-live": "assertive", children: successMessage }))] }), type != 'grid' ? (_jsx("div", { style: { height: '0.5px', background: 'rgb(var(--sf-color-line-color))', marginTop: '1rem' } })) : null] }));
|
|
251
|
+
return isAccordion ? (_jsx(AccordionWrapper, { title: l10n.getConstant('dataSource'), children: content })) : (content);
|
|
252
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ExpressionEditorProps {
|
|
3
|
+
expression?: string;
|
|
4
|
+
availableFields: string[] | any[];
|
|
5
|
+
onExpressionChange: (expression: string) => void;
|
|
6
|
+
}
|
|
7
|
+
export interface ColumnExpressionEditorProps {
|
|
8
|
+
expression?: string;
|
|
9
|
+
availableFields: string[];
|
|
10
|
+
onExpressionChange: (expression: string) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare const ExpressionEditor: React.FC<ExpressionEditorProps>;
|
|
13
|
+
export declare const ColumnExpressionEditor: React.FC<ColumnExpressionEditorProps>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
3
|
+
import { ExpressionEngine, ExpressionValidator } from '../../Common/expressions/ExpressionEngine';
|
|
4
|
+
import { floatBlur, floatFocus } from '../components/layout/LeftPane';
|
|
5
|
+
import { useLocalization } from '../context/LocalizationContext';
|
|
6
|
+
import { TextBoxComponent } from '@syncfusion/ej2-react-inputs';
|
|
7
|
+
export const ExpressionEditor = ({ expression = '', availableFields, onExpressionChange }) => {
|
|
8
|
+
const [localExpression, setLocalExpression] = useState(expression);
|
|
9
|
+
const [errors, setErrors] = useState([]);
|
|
10
|
+
const [hasDependencies, setHasDependencies] = useState(false);
|
|
11
|
+
const validator = new ExpressionValidator();
|
|
12
|
+
const engine = new ExpressionEngine();
|
|
13
|
+
// Validate expression and extract dependencies
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (localExpression.trim() === '') {
|
|
16
|
+
setErrors([]);
|
|
17
|
+
setHasDependencies(false);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const validationErrors = validator.validate(localExpression, availableFields);
|
|
21
|
+
const deps = engine.extractDependencies(localExpression);
|
|
22
|
+
const errorMessages = validationErrors.map(e => e.message);
|
|
23
|
+
// If no dependencies found, add error
|
|
24
|
+
if (deps.length === 0) {
|
|
25
|
+
errorMessages.push('Expression must contain at least one field reference');
|
|
26
|
+
}
|
|
27
|
+
setErrors(errorMessages);
|
|
28
|
+
setHasDependencies(deps.length > 0);
|
|
29
|
+
}, [localExpression, availableFields]);
|
|
30
|
+
// Update parent on change (even if there are validation errors)
|
|
31
|
+
// Syntax errors are shown but don't block expression from being saved
|
|
32
|
+
// Runtime validation will catch field reference errors at evaluation time
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (errors.length === 0 && hasDependencies) {
|
|
35
|
+
onExpressionChange(localExpression);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
onExpressionChange('');
|
|
39
|
+
}
|
|
40
|
+
}, [localExpression, onExpressionChange, hasDependencies, errors]);
|
|
41
|
+
// Show field suggestions when typing {
|
|
42
|
+
const handleChange = (e) => {
|
|
43
|
+
const value = e.target.value || e.value || '';
|
|
44
|
+
setLocalExpression(value);
|
|
45
|
+
};
|
|
46
|
+
return (_jsxs("div", { className: "expression-editor", children: [_jsx("div", { className: "expression-input-wrapper", children: _jsx("div", { className: "e-input-group", children: _jsx("textarea", { className: `e-input ${errors.length > 0 ? 'e-error' : ''}`, placeholder: "e.g., {qty} * {price}", onFocus: floatFocus, onBlur: floatBlur, value: localExpression, onChange: handleChange, "aria-label": 'Expression Editor textbox', "aria-invalid": errors.length > 0 ? 'true' : 'false' }) }) }), errors.length > 0 && (_jsx("div", { className: "expression-errors", role: "alert", "aria-live": "assertive", children: errors.map((error, idx) => (_jsx("div", { className: "e-error", role: "alert", "aria-live": "assertive", children: error }, idx))) }))] }));
|
|
47
|
+
};
|
|
48
|
+
export const ColumnExpressionEditor = ({ expression = '', availableFields, onExpressionChange }) => {
|
|
49
|
+
const [inputValue, setInputValue] = useState(expression);
|
|
50
|
+
const validator = useMemo(() => new ExpressionValidator(), []);
|
|
51
|
+
const engine = useMemo(() => new ExpressionEngine(), []);
|
|
52
|
+
const { l10n } = useLocalization();
|
|
53
|
+
// Validate only when expression is updated externally
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
setInputValue(expression);
|
|
56
|
+
}, [expression]);
|
|
57
|
+
// Validation errors
|
|
58
|
+
const errors = useMemo(() => {
|
|
59
|
+
if (!inputValue || inputValue.trim() === '') {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
const validationErrors = validator.validate(inputValue, availableFields);
|
|
63
|
+
const errorMessages = validationErrors.map(e => e.message);
|
|
64
|
+
// Check for field dependencies
|
|
65
|
+
const deps = engine.extractDependencies(inputValue);
|
|
66
|
+
// If no dependencies found, add error
|
|
67
|
+
if (deps.length === 0) {
|
|
68
|
+
errorMessages.push('Expression must contain at least one field reference');
|
|
69
|
+
}
|
|
70
|
+
return errorMessages;
|
|
71
|
+
}, [inputValue, availableFields, validator, engine]);
|
|
72
|
+
// Handle local input change
|
|
73
|
+
const handleInputChange = (e) => {
|
|
74
|
+
const newValue = e.value || '';
|
|
75
|
+
setInputValue(newValue);
|
|
76
|
+
// Validate before calling parent onChange
|
|
77
|
+
let tempErrors = [];
|
|
78
|
+
if (newValue && newValue.trim() !== '') {
|
|
79
|
+
const validationErrors = validator.validate(newValue, availableFields);
|
|
80
|
+
tempErrors = validationErrors.map(e => e.message);
|
|
81
|
+
// Check for field dependencies
|
|
82
|
+
const deps = engine.extractDependencies(newValue);
|
|
83
|
+
if (deps.length === 0) {
|
|
84
|
+
tempErrors.push('Expression must contain at least one field reference');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Only notify parent if no errors
|
|
88
|
+
if (tempErrors.length === 0) {
|
|
89
|
+
onExpressionChange(newValue);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
return (_jsxs("div", { "aria-label": 'Expression Editor textbox', className: "column-expression-editor", style: { display: 'flex', flexDirection: 'column' }, children: [_jsx(TextBoxComponent, { multiline: true, placeholder: l10n.getConstant("expressionPlaceholder"), value: inputValue, change: handleInputChange, className: "e-textbox expression-input", htmlAttributes: {
|
|
93
|
+
'aria-label': 'Expression Editor textbox',
|
|
94
|
+
'aria-invalid': errors.length > 0 ? 'true' : 'false',
|
|
95
|
+
...(errors.length > 0 && {
|
|
96
|
+
'aria-describedby': 'column-expression-errors'
|
|
97
|
+
})
|
|
98
|
+
} }), errors.length > 0 && (_jsx("div", { className: "e-error", id: "column-expression-errors", role: "alert", "aria-live": "assertive", children: errors.map((error, idx) => (_jsx("p", { style: { margin: 0 }, children: error }, idx))) }))] }));
|
|
99
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { FormSettings } from '../types/FormSchema';
|
|
3
|
+
interface FormSettingsPanelProps {
|
|
4
|
+
settings: FormSettings | undefined;
|
|
5
|
+
formSettingsConfig: any;
|
|
6
|
+
onSave: (settings: FormSettings, key: string) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare const FormSettingsPanel: React.FC<FormSettingsPanelProps>;
|
|
9
|
+
export {};
|