@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,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System clipboard helper for copying/pasting form field configurations
|
|
3
|
+
* Uses navigator.clipboard API for cross-tab and external tool integration
|
|
4
|
+
*/
|
|
5
|
+
export class ClipboardHelper {
|
|
6
|
+
static CLIPBOARD_PREFIX = 'FORMBUILDER_FIELD:';
|
|
7
|
+
/**
|
|
8
|
+
* Copy field configuration to system clipboard as JSON
|
|
9
|
+
*/
|
|
10
|
+
static async copyToClipboard(field) {
|
|
11
|
+
try {
|
|
12
|
+
const jsonString = JSON.stringify(field, null, 2);
|
|
13
|
+
const clipboardData = `${this.CLIPBOARD_PREFIX}${jsonString}`;
|
|
14
|
+
await navigator.clipboard.writeText(clipboardData);
|
|
15
|
+
//console.log('Field copied to clipboard:', field.id);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.error('Failed to copy to clipboard:', error);
|
|
19
|
+
throw new Error(`Unable to copy field: ${error}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Read field configuration from system clipboard
|
|
24
|
+
* Returns null if clipboard doesn't contain valid field data
|
|
25
|
+
*/
|
|
26
|
+
static async readFromClipboard() {
|
|
27
|
+
try {
|
|
28
|
+
const clipboardText = await navigator.clipboard.readText();
|
|
29
|
+
// Check if clipboard contains our prefixed data
|
|
30
|
+
if (!clipboardText.startsWith(this.CLIPBOARD_PREFIX)) {
|
|
31
|
+
console.warn('Clipboard does not contain form field data');
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
// Remove prefix and parse JSON
|
|
35
|
+
const jsonString = clipboardText.substring(this.CLIPBOARD_PREFIX.length);
|
|
36
|
+
const field = JSON.parse(jsonString);
|
|
37
|
+
//console.log('Field read from clipboard:', field.id);
|
|
38
|
+
return field;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error('Failed to read from clipboard:', error);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if clipboard contains valid form field data
|
|
47
|
+
*/
|
|
48
|
+
static async hasClipboardData() {
|
|
49
|
+
try {
|
|
50
|
+
const clipboardText = await navigator.clipboard.readText();
|
|
51
|
+
return clipboardText.startsWith(this.CLIPBOARD_PREFIX);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// Silently return false when document is not focused (expected behavior)
|
|
55
|
+
// or clipboard permissions are denied
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Generate new unique field with modified ID and name
|
|
61
|
+
*/
|
|
62
|
+
static createUniqueField(sourceField) {
|
|
63
|
+
const timestamp = Date.now();
|
|
64
|
+
const random = Math.floor(Math.random() * 1000);
|
|
65
|
+
return {
|
|
66
|
+
...sourceField,
|
|
67
|
+
id: `${sourceField.type}_${timestamp}_${random}`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
static async clearData() {
|
|
71
|
+
// Overwrite the system clipboard with empty text unconditionally.
|
|
72
|
+
//NOTE: We deliberately do NOT call navigator.clipboard.readText() here
|
|
73
|
+
// because doing so triggers the browser's native paste UI prompt in
|
|
74
|
+
// Firefox on first invocation. Writing alone is sufficient to clear
|
|
75
|
+
// any form-builder data we previously wrote.
|
|
76
|
+
try {
|
|
77
|
+
await navigator.clipboard.writeText('');
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
// Silently ignore failures (e.g. document not focused)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { GridColumn } from '../types/FormSchema';
|
|
3
|
+
interface ColumnBuilderProps {
|
|
4
|
+
columns: GridColumn[];
|
|
5
|
+
onChange: (columns: GridColumn[]) => void;
|
|
6
|
+
name?: any;
|
|
7
|
+
isConditionData?: boolean;
|
|
8
|
+
label?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const ColumnBuilder: React.FC<ColumnBuilderProps>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useRef } from 'react';
|
|
3
|
+
import { ButtonComponent, CheckBoxComponent } from '@syncfusion/ej2-react-buttons';
|
|
4
|
+
import { AutoCompleteComponent, DropDownListComponent } from '@syncfusion/ej2-react-dropdowns';
|
|
5
|
+
import { NumericTextBoxComponent, TextBoxComponent } from '@syncfusion/ej2-react-inputs';
|
|
6
|
+
import { DatePickerComponent } from '@syncfusion/ej2-react-calendars';
|
|
7
|
+
import { useLocalization } from '../context/LocalizationContext';
|
|
8
|
+
import { useFetchOptions } from '../../Common/utils';
|
|
9
|
+
import { useFormSchema } from '../context/FormSchemaContext';
|
|
10
|
+
import { ColumnExpressionEditor } from './ExpressionEditor';
|
|
11
|
+
import { CustomValidationEditor } from './CustomValidationEditor';
|
|
12
|
+
import { AccordionWrapper } from './CustomAccordion';
|
|
13
|
+
export const ColumnBuilder = ({ columns, onChange, name, isConditionData, label }) => {
|
|
14
|
+
const [rows, setRows] = useState([]);
|
|
15
|
+
const [expandedId, setExpandedId] = useState(null);
|
|
16
|
+
const nextIdRef = useRef(0);
|
|
17
|
+
const { l10n, locale } = useLocalization();
|
|
18
|
+
const { formSchema } = useFormSchema();
|
|
19
|
+
const componentForEdit = formSchema.components.find(comp => comp.name === name);
|
|
20
|
+
const dataSource = componentForEdit?.dataSource || [];
|
|
21
|
+
const fetchedData = useFetchOptions(dataSource);
|
|
22
|
+
const extractDataSourceKeys = (dataSource) => {
|
|
23
|
+
if (Array.isArray(dataSource) && dataSource.length > 0) {
|
|
24
|
+
return Object.keys(dataSource[0]);
|
|
25
|
+
}
|
|
26
|
+
return [];
|
|
27
|
+
};
|
|
28
|
+
const dataSourceKeys = extractDataSourceKeys(Array.isArray(dataSource) ? dataSource : fetchedData);
|
|
29
|
+
const columnsWithKeys = columns.map(col => col.field).filter(field => field);
|
|
30
|
+
const mapTypeToEditType = (type) => {
|
|
31
|
+
switch (type) {
|
|
32
|
+
case 'number':
|
|
33
|
+
return 'numericedit';
|
|
34
|
+
case 'date':
|
|
35
|
+
return 'datepickeredit';
|
|
36
|
+
case 'boolean':
|
|
37
|
+
return 'booleanedit';
|
|
38
|
+
default:
|
|
39
|
+
return 'textbox';
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
// Initialize rows from columns
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const initialRows = (columns || []).map((col, idx) => {
|
|
45
|
+
const id = idx;
|
|
46
|
+
if (id >= nextIdRef.current) {
|
|
47
|
+
nextIdRef.current = id + 1;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
id,
|
|
51
|
+
index: idx + 1,
|
|
52
|
+
...col,
|
|
53
|
+
allowEditing: col.allowEditing !== undefined ? col.allowEditing : true // Add this line
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
setRows(initialRows);
|
|
57
|
+
}, []);
|
|
58
|
+
// Update parent when columns change
|
|
59
|
+
const updateColumns = (newRows) => {
|
|
60
|
+
const updatedColumns = newRows.map(({ id, index, ...col }) => col);
|
|
61
|
+
onChange(updatedColumns);
|
|
62
|
+
};
|
|
63
|
+
const handleAddColumn = () => {
|
|
64
|
+
const newId = nextIdRef.current++;
|
|
65
|
+
const newIndex = rows.length + 1;
|
|
66
|
+
const newColumn = {
|
|
67
|
+
id: newId,
|
|
68
|
+
index: newIndex,
|
|
69
|
+
field: `column_${Date.now()}`,
|
|
70
|
+
headerText: `${l10n.getConstant('column')} ${newIndex}`,
|
|
71
|
+
type: 'string',
|
|
72
|
+
editType: mapTypeToEditType('string'),
|
|
73
|
+
width: '120',
|
|
74
|
+
allowEditing: true,
|
|
75
|
+
validationRules: {}
|
|
76
|
+
};
|
|
77
|
+
const newRows = [...rows, newColumn];
|
|
78
|
+
setRows(newRows);
|
|
79
|
+
updateColumns(newRows);
|
|
80
|
+
};
|
|
81
|
+
const handleDeleteColumn = (id) => {
|
|
82
|
+
const newRows = rows.filter(row => row.id !== id).map((row, idx) => ({
|
|
83
|
+
...row,
|
|
84
|
+
index: idx + 1
|
|
85
|
+
}));
|
|
86
|
+
setRows(newRows);
|
|
87
|
+
updateColumns(newRows);
|
|
88
|
+
};
|
|
89
|
+
const handleColumnUpdate = (id, updates) => {
|
|
90
|
+
const newRows = rows.map(row => row.id === id ? { ...row, ...updates } : row);
|
|
91
|
+
setRows(newRows);
|
|
92
|
+
updateColumns(newRows);
|
|
93
|
+
};
|
|
94
|
+
const handleValidationRuleChange = (id, ruleName, value) => {
|
|
95
|
+
const newRows = rows.map(row => {
|
|
96
|
+
if (row.id === id) {
|
|
97
|
+
const newValidationRules = { ...row.validationRules };
|
|
98
|
+
if (value === undefined || value === '' || value === null) {
|
|
99
|
+
delete newValidationRules[ruleName];
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
newValidationRules[ruleName] = value;
|
|
103
|
+
}
|
|
104
|
+
return { ...row, validationRules: newValidationRules };
|
|
105
|
+
}
|
|
106
|
+
return row;
|
|
107
|
+
});
|
|
108
|
+
setRows(newRows);
|
|
109
|
+
updateColumns(newRows);
|
|
110
|
+
};
|
|
111
|
+
const handlePrimaryKeyChange = (id, checked) => {
|
|
112
|
+
const newRows = rows.map(row => {
|
|
113
|
+
if (row.id === id) {
|
|
114
|
+
// Set this column as primary key
|
|
115
|
+
return { ...row, isPrimaryKey: checked };
|
|
116
|
+
}
|
|
117
|
+
else if (checked) {
|
|
118
|
+
// Clear primary key from all other columns when setting a new one
|
|
119
|
+
return { ...row, isPrimaryKey: false };
|
|
120
|
+
}
|
|
121
|
+
return row;
|
|
122
|
+
});
|
|
123
|
+
setRows(newRows);
|
|
124
|
+
updateColumns(newRows);
|
|
125
|
+
};
|
|
126
|
+
const hasOtherPrimaryKey = (currentId) => {
|
|
127
|
+
return rows.some(row => row.id !== currentId && row.isPrimaryKey === true);
|
|
128
|
+
};
|
|
129
|
+
return (_jsx(AccordionWrapper, { title: l10n.getConstant(label), children: _jsxs("div", { className: "column-builder", children: [_jsx("div", { className: "column-list", style: rows.length ? undefined : { display: 'none' }, children: rows.map((col) => (_jsxs("div", { className: "column-item", children: [_jsxs("div", { className: `column-header ${isConditionData ? 'no-border-radius' : ''}`, children: [_jsx("span", { className: "column-field", children: col.headerText }), _jsxs("div", { style: { display: 'flex', gap: '8px' }, children: [_jsx(ButtonComponent, { onClick: () => setExpandedId(expandedId === col.id ? null : col.id), cssClass: 'e-flat', iconCss: "e-icons e-edit", title: l10n.getConstant('edit'), "aria-label": l10n.getConstant('edit') }), _jsx(ButtonComponent, { onClick: (e) => {
|
|
130
|
+
e.stopPropagation();
|
|
131
|
+
handleDeleteColumn(col.id);
|
|
132
|
+
}, cssClass: 'e-flat', iconCss: "e-icons e-trash", title: l10n.getConstant('delete'), "aria-label": l10n.getConstant('delete') })] })] }), expandedId === col.id && (_jsxs("div", { className: !isConditionData ? 'right-sidebar-column' : 'conditional-column', children: [!isConditionData &&
|
|
133
|
+
_jsxs("div", { className: "column-sidebar-header", children: [_jsx("h3", { children: l10n.getConstant('columnSettings') }), _jsx("button", { className: "close-btn", onClick: () => setExpandedId(expandedId === col.id ? null : col.id), title: "Close (Esc)", "aria-label": l10n.getConstant('close'), children: "\u2715" })] }, 'column settings'), _jsxs("div", { className: 'column-details', children: [_jsxs("div", { className: "form-group", children: [_jsx("label", { className: 'property-label', style: { fontWeight: 600, marginBottom: '0.5rem', fontSize: '14px' }, children: l10n.getConstant('basic') }), _jsx("label", { htmlFor: `col-field-${col.id}`, children: l10n.getConstant('fieldName') }), dataSourceKeys.length > 0 ? (_jsx(AutoCompleteComponent, { dataSource: dataSourceKeys.map(key => ({ text: key, value: key })), fields: { text: 'text', value: 'value' }, value: col.field, change: (e) => handleColumnUpdate(col.id, { field: e.value }), htmlAttributes: { 'aria-label': l10n.getConstant('fieldName') } })) : (_jsx(TextBoxComponent, { type: "text", value: col.field, onChange: (e) => handleColumnUpdate(col.id, { field: e.value }), htmlAttributes: { 'aria-label': l10n.getConstant('fieldName') } }))] }), _jsxs("div", { className: "form-group", children: [_jsx("label", { htmlFor: `col-header-${col.id}`, children: l10n.getConstant('headerText') }), _jsx(TextBoxComponent, { type: "text", value: col.headerText, onChange: (e) => handleColumnUpdate(col.id, { headerText: e.value }), placeholder: l10n.getConstant('columnHeaderDisplay'), className: "e-input-group e-control-wrapper e-textbox ", htmlAttributes: { 'aria-label': l10n.getConstant('headerText') } })] }), _jsxs("div", { className: "form-group", children: [_jsx("label", { htmlFor: `col-type-${col.id}`, children: l10n.getConstant('type') }), _jsx(DropDownListComponent, { dataSource: [
|
|
134
|
+
{ text: l10n.getConstant('string'), value: 'string' },
|
|
135
|
+
{ text: l10n.getConstant('number'), value: 'number' },
|
|
136
|
+
{ text: l10n.getConstant('date'), value: 'date' },
|
|
137
|
+
{ text: l10n.getConstant('boolean'), value: 'boolean' }
|
|
138
|
+
], fields: { text: 'text', value: 'value' }, value: col.type || 'string', change: (e) => {
|
|
139
|
+
const newType = e.value || 'string';
|
|
140
|
+
handleColumnUpdate(col.id, {
|
|
141
|
+
type: newType,
|
|
142
|
+
editType: mapTypeToEditType(newType),
|
|
143
|
+
validationRules: {}
|
|
144
|
+
});
|
|
145
|
+
}, placeholder: l10n.getConstant('selectType'), cssClass: "e-textbox", htmlAttributes: { 'aria-label': l10n.getConstant('type') } })] }), _jsxs("div", { className: "form-group", children: [_jsx("label", { htmlFor: `col-width-${col.id}`, children: l10n.getConstant('width') }), _jsx(TextBoxComponent, { type: "text", value: `${col.width}` || '120', onChange: (e) => handleColumnUpdate(col.id, { width: e.value }), placeholder: l10n.getConstant('columnWidthPlaceholder'), className: "e-textbox", htmlAttributes: { 'aria-label': l10n.getConstant('width') } })] }), _jsxs("div", { className: "form-group", children: [_jsx("label", { htmlFor: `col-format-${col.id}`, children: l10n.getConstant('format') }), _jsx(TextBoxComponent, { type: "text", value: col.format || '', onChange: (e) => handleColumnUpdate(col.id, { format: e.value }), className: "e-textbox", htmlAttributes: { 'aria-label': l10n.getConstant('format') } })] }), _jsx("div", { style: { height: '0.5px', borderBottom: '0.5px solid rgb(var(--sf-color-line-color))' } }), _jsxs("div", { className: "form-group checkbox-group", children: [_jsx("label", { className: 'property-label', style: { fontWeight: 600, marginBottom: '0.5rem', fontSize: '14px' }, children: l10n.getConstant('behaviour') }), _jsxs("label", { children: [_jsx(CheckBoxComponent, { type: "checkbox", checked: col.isPrimaryKey || false, disabled: !col.isPrimaryKey && hasOtherPrimaryKey(col.id), change: (e) => handlePrimaryKeyChange(col.id, e.checked || false), htmlAttributes: { 'aria-label': l10n.getConstant('primaryKey') } }), l10n.getConstant('primaryKey'), !col.isPrimaryKey &&
|
|
146
|
+
hasOtherPrimaryKey(col.id) &&
|
|
147
|
+
` (${l10n.getConstant('anotherPrimaryKeySet')})`] })] }), _jsx("div", { className: "form-group checkbox-group", children: _jsxs("label", { children: [_jsx(CheckBoxComponent, { type: "checkbox", checked: col.allowEditing !== false, change: (e) => handleColumnUpdate(col.id, { allowEditing: e.checked || false }), htmlAttributes: { 'aria-label': l10n.getConstant('allowEditing') || 'Allow Editing' } }), l10n.getConstant('allowEditing') || 'Allow Editing'] }) }), _jsx("div", { style: { height: '0.5px', borderBottom: '0.5px solid rgb(var(--sf-color-line-color))' } }), _jsx("div", { children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '1rem' }, children: [_jsx("label", { style: { fontWeight: 600, fontSize: '14px' }, id: `validation-rules-label-${col.id}`, children: l10n.getConstant('validationRulesFor') }), _jsx("div", { className: "form-group checkbox-group", children: _jsxs("label", { children: [_jsx(CheckBoxComponent, { type: "checkbox", checked: col.validationRules?.required || false, change: (e) => handleValidationRuleChange(col.id, 'required', e.checked || undefined), htmlAttributes: { 'aria-label': l10n.getConstant('required') } }), l10n.getConstant('required')] }) }), col.type === 'string' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "form-group", children: [_jsx("label", { htmlFor: `col-minLength-${col.id}`, children: l10n.getConstant('minLength') }), _jsx(NumericTextBoxComponent, { value: col.validationRules?.minLength ?? undefined, locale: locale, change: (e) => handleValidationRuleChange(col.id, 'minLength', e.value !== null && e.value !== undefined ? e.value : undefined), placeholder: l10n.getConstant('minimumCharacters'), cssClass: "e-textbox", htmlAttributes: { 'aria-label': l10n.getConstant('minLength') } })] }), _jsxs("div", { className: "form-group", children: [_jsx("label", { htmlFor: `col-maxLength-${col.id}`, children: l10n.getConstant('maxLength') }), _jsx(NumericTextBoxComponent, { value: col.validationRules?.maxLength ?? undefined, locale: locale, change: (e) => handleValidationRuleChange(col.id, 'maxLength', e.value !== null && e.value !== undefined ? e.value : undefined), placeholder: l10n.getConstant('maximumCharacters'), cssClass: "e-textbox", htmlAttributes: { 'aria-label': l10n.getConstant('maxLength') } })] })] })), col.type === 'number' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "form-group", children: [_jsx("label", { htmlFor: `col-minValue-${col.id}`, children: l10n.getConstant('minValue') }), _jsx(NumericTextBoxComponent, { locale: locale, value: col.validationRules?.min ?? undefined, change: (e) => handleValidationRuleChange(col.id, 'min', e.value !== null && e.value !== undefined ? e.value : undefined), placeholder: l10n.getConstant('minimumValue'), cssClass: "e-textbox", htmlAttributes: { 'aria-label': l10n.getConstant('minValue') } })] }), _jsxs("div", { className: "form-group", children: [_jsx("label", { htmlFor: `col-maxValue-${col.id}`, children: l10n.getConstant('maxValue') }), _jsx(NumericTextBoxComponent, { value: col.validationRules?.max ?? undefined, locale: locale, change: (e) => handleValidationRuleChange(col.id, 'max', e.value !== null && e.value !== undefined ? e.value : undefined), placeholder: l10n.getConstant('maximumValue'), cssClass: "e-textbox", htmlAttributes: { 'aria-label': l10n.getConstant('maxValue') } })] })] })), col.type === 'date' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "form-group", children: [_jsx("label", { htmlFor: `col-minDate-${col.id}`, children: l10n.getConstant('minDate') }), _jsx(DatePickerComponent, { locale: locale, value: col.validationRules?.min ? new Date(col.validationRules.min) : undefined, change: (e) => handleValidationRuleChange(col.id, 'min', e.value ? e.value.toISOString().slice(0, 10) : undefined), cssClass: "e-textbox", htmlAttributes: { 'aria-label': l10n.getConstant('minDate') } })] }), _jsxs("div", { className: "form-group", children: [_jsx("label", { htmlFor: `col-maxDate-${col.id}`, children: l10n.getConstant('maxDate') }), _jsx(DatePickerComponent, { locale: locale, value: col.validationRules?.max ? new Date(col.validationRules.max) : undefined, change: (e) => handleValidationRuleChange(col.id, 'max', e.value ? e.value.toISOString().slice(0, 10) : undefined), cssClass: "e-textbox", htmlAttributes: { 'aria-label': l10n.getConstant('maxDate') } })] })] }))] }) }), _jsx("div", { style: { height: '0.5px', borderBottom: '0.5px solid rgb(var(--sf-color-line-color))' } }), _jsxs("div", { className: "form-group", children: [_jsx("label", { style: { fontWeight: 600, marginBottom: '0.5rem', fontSize: '14px' }, htmlFor: `col-expression-${col.id}`, children: l10n.getConstant('expression') }), _jsxs("div", { className: "expression-editor-wrapper", children: [_jsx(ColumnExpressionEditor, { expression: col.expression || '', availableFields: columnsWithKeys, onExpressionChange: (expr) => handleColumnUpdate(col.id, { expression: expr }) }), col.expression && col.expression.trim() && (_jsx("small", { style: { color: '#666', marginTop: '8px', display: 'block' }, children: "Column is read-only (if calculated)." }))] })] }), _jsx("div", { style: { height: '0.5px', borderBottom: '0.5px solid rgb(var(--sf-color-line-color))' } }), _jsx("div", { className: "form-group", children: _jsx("div", { className: "custom-validation-section", children: _jsx(CustomValidationEditor, { componentFields: rows.map((r, _idx) => ({
|
|
148
|
+
id: `${r.id}`,
|
|
149
|
+
name: r.field,
|
|
150
|
+
type: r.type || 'string',
|
|
151
|
+
label: r.headerText
|
|
152
|
+
})), excludeFieldId: col.field, existingRules: col.customValidation || [], values: { "label": "customValidation" }, onChange: (rules) => handleColumnUpdate(col.id, { customValidation: rules }) }) }) })] })] }))] }, col.id))) }), _jsx("div", { className: "", style: { width: '100%' }, children: _jsx(ButtonComponent, { style: { width: '100%' }, cssClass: "e-outline", onClick: handleAddColumn, type: "button", "aria-label": l10n.getConstant('addColumn'), children: l10n.getConstant('addColumn') }) })] }) }));
|
|
153
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useRef } from 'react';
|
|
3
|
+
import { GridComponent, ColumnsDirective, ColumnDirective, Inject, Page } from '@syncfusion/ej2-react-grids';
|
|
4
|
+
import { splitWidths } from '../../Common/utils';
|
|
5
|
+
import { useLocalization } from '../context/LocalizationContext';
|
|
6
|
+
import { floatFocus } from '../components/layout/LeftPane';
|
|
7
|
+
export const ColumnSpanEditor = ({ columnCount, initialWidths, onChange, }) => {
|
|
8
|
+
const [rows, setRows] = useState([]);
|
|
9
|
+
const [maxValues, setMaxValues] = useState({});
|
|
10
|
+
const gridRef = useRef(null);
|
|
11
|
+
const { l10n } = useLocalization();
|
|
12
|
+
const getInitialSplitWidths = (columnCount, initialWidths) => {
|
|
13
|
+
const normalized = Array.from({ length: columnCount }, (_, i) => initialWidths && initialWidths[i] != null ? initialWidths[i] : null);
|
|
14
|
+
return splitWidths(normalized);
|
|
15
|
+
};
|
|
16
|
+
// Initialize rows based on column count and initial widths
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (columnCount < 1 || columnCount > 12) {
|
|
19
|
+
console.warn(`Invalid column count: ${columnCount}`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const resolvedWidths = getInitialSplitWidths(columnCount, initialWidths);
|
|
23
|
+
// Create grid rows - no auto-fill, just use provided values or empty strings
|
|
24
|
+
const newRows = Array(columnCount)
|
|
25
|
+
.fill(null)
|
|
26
|
+
.map((_, idx) => ({
|
|
27
|
+
id: idx,
|
|
28
|
+
columnName: `${idx + 1}`,
|
|
29
|
+
width: ((initialWidths && !initialWidths.every((w) => w === null))) ? (initialWidths[idx] ? String(initialWidths[idx]) : '') : resolvedWidths[idx] ? String(resolvedWidths[idx]) : '',
|
|
30
|
+
}));
|
|
31
|
+
setRows(newRows);
|
|
32
|
+
calculateMaxValues(newRows);
|
|
33
|
+
}, [columnCount, initialWidths]);
|
|
34
|
+
// Calculate max value for each field (100 - sum of all other fields)
|
|
35
|
+
const calculateMaxValues = (rowsData) => {
|
|
36
|
+
const filledValues = rowsData
|
|
37
|
+
.map((row) => (row.width ? parseFloat(row.width) : 0))
|
|
38
|
+
.filter((val) => val > 0);
|
|
39
|
+
const totalFilled = filledValues.reduce((sum, val) => sum + val, 0);
|
|
40
|
+
const newMaxValues = {};
|
|
41
|
+
rowsData.forEach((row) => {
|
|
42
|
+
const currentValue = row.width ? parseFloat(row.width) : 0;
|
|
43
|
+
const othersFilled = totalFilled - currentValue;
|
|
44
|
+
newMaxValues[row.id] = Math.max(0, 100 - othersFilled);
|
|
45
|
+
});
|
|
46
|
+
setMaxValues(newMaxValues);
|
|
47
|
+
};
|
|
48
|
+
// Handle width change in grid
|
|
49
|
+
const handleWidthChange = (id, newValue) => {
|
|
50
|
+
// Allow empty string or valid numeric values
|
|
51
|
+
if (newValue === '' || newValue.trim() === '') {
|
|
52
|
+
const updatedRows = rows.map((row) => row.id === id ? { ...row, width: '' } : row);
|
|
53
|
+
setRows(updatedRows);
|
|
54
|
+
calculateMaxValues(updatedRows);
|
|
55
|
+
triggerChange(updatedRows);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let parsedWidth = parseFloat(newValue);
|
|
59
|
+
// Validate: must be positive number
|
|
60
|
+
if (isNaN(parsedWidth) || parsedWidth <= 0) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Auto-correct if exceeds max - set to max value
|
|
64
|
+
const maxForField = (maxValues[id] != undefined) ? maxValues[id] : 100;
|
|
65
|
+
if (parsedWidth > maxForField) {
|
|
66
|
+
parsedWidth = maxForField;
|
|
67
|
+
}
|
|
68
|
+
const updatedRows = rows.map((row) => row.id === id ? { ...row, width: String(parsedWidth) } : row);
|
|
69
|
+
setRows(updatedRows);
|
|
70
|
+
calculateMaxValues(updatedRows);
|
|
71
|
+
triggerChange(updatedRows);
|
|
72
|
+
};
|
|
73
|
+
// Trigger onChange callback with widths array (null for empty values)
|
|
74
|
+
const triggerChange = (rowsData) => {
|
|
75
|
+
const widths = rowsData.map((row) => row.width ? parseFloat(row.width) : null);
|
|
76
|
+
onChange(widths);
|
|
77
|
+
};
|
|
78
|
+
// Width input template - text field with dynamic max value
|
|
79
|
+
const widthTemplate = (props) => {
|
|
80
|
+
const maxForField = maxValues[props.id] || 100;
|
|
81
|
+
return (_jsx("div", { className: "e-input-group", children: _jsx("input", { type: 'number', defaultValue: props.width, max: maxForField, onFocus: floatFocus, "aria-label": "Span percentage", onKeyDown: (e) => {
|
|
82
|
+
if (e.ctrlKey && e.key === 'a') {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
e.currentTarget.select();
|
|
85
|
+
}
|
|
86
|
+
}, onBlur: (e) => handleWidthChange(props.id, e.target.value), placeholder: "Width %", className: "e-input" }) }));
|
|
87
|
+
};
|
|
88
|
+
// Calculate grid height based on number of rows
|
|
89
|
+
const calculateHeight = (rowCount) => {
|
|
90
|
+
const baseHeight = 58; // Header + 1 row
|
|
91
|
+
const rowHeight = 42;
|
|
92
|
+
return baseHeight + rowCount * rowHeight;
|
|
93
|
+
};
|
|
94
|
+
if (columnCount < 1 || columnCount > 12) {
|
|
95
|
+
return (_jsx("div", { className: "column-span-editor-error", children: _jsxs("span", { children: ["\u26A0\uFE0F Invalid column count: ", columnCount] }) }));
|
|
96
|
+
}
|
|
97
|
+
return (_jsx("div", { className: "options-editor", children: _jsxs(GridComponent, { ref: gridRef, dataSource: rows, allowPaging: false, height: calculateHeight(rows.length), allowSelection: false, cssClass: "options-grid table-col", children: [_jsxs(ColumnsDirective, { children: [_jsx(ColumnDirective, { field: "columnName", headerText: l10n.getConstant('columns') || "Columns", width: "80", textAlign: "Left", allowSorting: false, allowResizing: false }), _jsx(ColumnDirective, { field: "width", headerText: `${l10n.getConstant('width') || 'Width'} (%)`, width: "auto", template: widthTemplate, allowSorting: false, allowResizing: false })] }), _jsx(Inject, { services: [Page] })] }) }));
|
|
98
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { FormComponent, ConditionalDataRule } from '../types/FormSchema';
|
|
3
|
+
export interface ConditionDataEditorProps {
|
|
4
|
+
/** Array of available form field names/components for condition rules */
|
|
5
|
+
componentFields: FormComponent[];
|
|
6
|
+
/** The DataGrid component being edited */
|
|
7
|
+
componentForEdit?: FormComponent;
|
|
8
|
+
/** ID of the DataGrid being edited - exclude from columns */
|
|
9
|
+
excludeFieldId?: string;
|
|
10
|
+
/** Existing conditional data rules to pre-populate */
|
|
11
|
+
existingRules?: ConditionalDataRule[];
|
|
12
|
+
/** Callback when conditions change */
|
|
13
|
+
onChange: (rules: ConditionalDataRule[]) => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* ConditionDataEditor Component
|
|
17
|
+
*
|
|
18
|
+
* Allows users to define conditional data sources for DataGrid components.
|
|
19
|
+
* Users can create multiple conditions, each with its own QueryBuilder rule
|
|
20
|
+
* and associated data source configuration.
|
|
21
|
+
*
|
|
22
|
+
* Features:
|
|
23
|
+
* - Add/remove multiple conditional data rules
|
|
24
|
+
* - QueryBuilder for condition definition
|
|
25
|
+
* - DataSourceCollector for data source configuration
|
|
26
|
+
* - Expandable UI (similar to ColumnBuilder pattern)
|
|
27
|
+
* - Persists state to FormSchema
|
|
28
|
+
*/
|
|
29
|
+
export declare const ConditionDataEditor: React.FC<ConditionDataEditorProps>;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useRef, useEffect, useState } from 'react';
|
|
3
|
+
import { QueryBuilderComponent } from '@syncfusion/ej2-react-querybuilder';
|
|
4
|
+
import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
|
|
5
|
+
import { useLocalization } from '../context/LocalizationContext';
|
|
6
|
+
import { DataSourceCollector } from './DataSourceCollector';
|
|
7
|
+
import { ColumnBuilder } from './ColumnBuilder';
|
|
8
|
+
import { createColumnTemplate } from './ConditionalEditorUtils';
|
|
9
|
+
import { AccordionWrapper } from './CustomAccordion';
|
|
10
|
+
/**
|
|
11
|
+
* ConditionDataEditor Component
|
|
12
|
+
*
|
|
13
|
+
* Allows users to define conditional data sources for DataGrid components.
|
|
14
|
+
* Users can create multiple conditions, each with its own QueryBuilder rule
|
|
15
|
+
* and associated data source configuration.
|
|
16
|
+
*
|
|
17
|
+
* Features:
|
|
18
|
+
* - Add/remove multiple conditional data rules
|
|
19
|
+
* - QueryBuilder for condition definition
|
|
20
|
+
* - DataSourceCollector for data source configuration
|
|
21
|
+
* - Expandable UI (similar to ColumnBuilder pattern)
|
|
22
|
+
* - Persists state to FormSchema
|
|
23
|
+
*/
|
|
24
|
+
export const ConditionDataEditor = ({ componentFields, excludeFieldId, existingRules = [], onChange }) => {
|
|
25
|
+
const { l10n } = useLocalization();
|
|
26
|
+
const stateRef = useRef({
|
|
27
|
+
rules: existingRules,
|
|
28
|
+
expandedRuleId: null,
|
|
29
|
+
nextRuleId: existingRules.length
|
|
30
|
+
});
|
|
31
|
+
const qbRefs = useRef({});
|
|
32
|
+
const [, forceRender] = useState({});
|
|
33
|
+
// Initialize QueryBuilder refs for each rule
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
stateRef.current.rules.forEach((_, idx) => {
|
|
36
|
+
if (!qbRefs.current[idx]) {
|
|
37
|
+
qbRefs.current[idx] = React.createRef();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}, [stateRef.current.rules.length]);
|
|
41
|
+
/**
|
|
42
|
+
* Build columns for QueryBuilder - excludes the current DataGrid field
|
|
43
|
+
* to prevent self-referential conditions
|
|
44
|
+
* Includes templates for different field types (dropdown, date, time, etc.)
|
|
45
|
+
*/
|
|
46
|
+
const buildColumnsForQueryBuilder = (qbRef) => {
|
|
47
|
+
return componentFields
|
|
48
|
+
.filter(component => component.id !== excludeFieldId)
|
|
49
|
+
.map(component => {
|
|
50
|
+
const fieldType = inferFieldType(component);
|
|
51
|
+
const operators = [
|
|
52
|
+
{ key: 'Is Empty', value: 'isempty' },
|
|
53
|
+
{ key: 'Is Not Empty', value: 'isnotempty' },
|
|
54
|
+
{ key: 'Equal', value: 'equal' },
|
|
55
|
+
{ key: 'Not Equal', value: 'notequal' },
|
|
56
|
+
...(fieldType === 'number' || fieldType === 'date'
|
|
57
|
+
? [
|
|
58
|
+
{ key: 'Greater than', value: 'greaterthan' },
|
|
59
|
+
{ key: 'Less than', value: 'lessthan' },
|
|
60
|
+
{ key: 'Greater than or equal', value: 'greaterthanorequal' },
|
|
61
|
+
{ key: 'Less than or equal', value: 'lessthanorequal' }
|
|
62
|
+
]
|
|
63
|
+
: []),
|
|
64
|
+
...(fieldType === 'string'
|
|
65
|
+
? [
|
|
66
|
+
{ key: 'Starts With', value: 'startswith' },
|
|
67
|
+
{ key: 'Contains', value: 'contains' },
|
|
68
|
+
{ key: 'Not Contains', value: 'notcontains' }
|
|
69
|
+
]
|
|
70
|
+
: [])
|
|
71
|
+
];
|
|
72
|
+
return {
|
|
73
|
+
field: component.id,
|
|
74
|
+
label: component.label || component.id,
|
|
75
|
+
type: fieldType,
|
|
76
|
+
format: fieldType === 'date' ? 'MM/dd/yyyy' : undefined,
|
|
77
|
+
operators,
|
|
78
|
+
template: createColumnTemplate(component, qbRef)
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Infer field type from component type
|
|
84
|
+
* Used for QueryBuilder column configuration
|
|
85
|
+
*/
|
|
86
|
+
const inferFieldType = (component) => {
|
|
87
|
+
const type = component.type;
|
|
88
|
+
if (type === 'number' || type === 'rangeSlider' || type === 'rating')
|
|
89
|
+
return 'number';
|
|
90
|
+
if (type === 'date')
|
|
91
|
+
return 'date';
|
|
92
|
+
if (type === 'checkbox' || type === 'switch')
|
|
93
|
+
return 'boolean';
|
|
94
|
+
return 'string';
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Handle rule condition change from QueryBuilder
|
|
98
|
+
*/
|
|
99
|
+
const handleRuleConditionChange = (ruleIndex) => {
|
|
100
|
+
const qbRef = qbRefs.current[ruleIndex];
|
|
101
|
+
if (qbRef?.current) {
|
|
102
|
+
const rule = qbRef.current.getValidRules(qbRef.current.rule);
|
|
103
|
+
stateRef.current.rules[ruleIndex] = {
|
|
104
|
+
...stateRef.current.rules[ruleIndex],
|
|
105
|
+
condition: rule
|
|
106
|
+
};
|
|
107
|
+
onChange(stateRef.current.rules);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Handle data source change from DataSourceCollector
|
|
112
|
+
*/
|
|
113
|
+
const handleDataSourceChange = (ruleIndex, dataSource) => {
|
|
114
|
+
stateRef.current.rules[ruleIndex] = {
|
|
115
|
+
...stateRef.current.rules[ruleIndex],
|
|
116
|
+
dataSource
|
|
117
|
+
};
|
|
118
|
+
onChange(stateRef.current.rules);
|
|
119
|
+
forceRender({});
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Handle columns change from ColumnBuilder
|
|
123
|
+
*/
|
|
124
|
+
const handleColumnsChange = (ruleIndex, columns) => {
|
|
125
|
+
stateRef.current.rules[ruleIndex] = {
|
|
126
|
+
...stateRef.current.rules[ruleIndex],
|
|
127
|
+
columns
|
|
128
|
+
};
|
|
129
|
+
onChange(stateRef.current.rules);
|
|
130
|
+
forceRender({});
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Add new conditional data rule
|
|
134
|
+
*/
|
|
135
|
+
const handleAddRule = () => {
|
|
136
|
+
const newRuleId = stateRef.current.nextRuleId;
|
|
137
|
+
const newRule = {
|
|
138
|
+
condition: { condition: 'and', rules: [] },
|
|
139
|
+
dataSource: []
|
|
140
|
+
};
|
|
141
|
+
stateRef.current.rules.push(newRule);
|
|
142
|
+
stateRef.current.expandedRuleId = newRuleId;
|
|
143
|
+
stateRef.current.nextRuleId = newRuleId + 1;
|
|
144
|
+
qbRefs.current[newRuleId] = React.createRef();
|
|
145
|
+
onChange(stateRef.current.rules);
|
|
146
|
+
forceRender({});
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Delete conditional data rule
|
|
150
|
+
*/
|
|
151
|
+
const handleDeleteRule = (ruleIndex) => {
|
|
152
|
+
stateRef.current.rules.splice(ruleIndex, 1);
|
|
153
|
+
// Clean up refs
|
|
154
|
+
delete qbRefs.current[ruleIndex];
|
|
155
|
+
stateRef.current.expandedRuleId = null;
|
|
156
|
+
onChange(stateRef.current.rules);
|
|
157
|
+
forceRender({});
|
|
158
|
+
};
|
|
159
|
+
return (_jsxs("div", { className: "condition-data-editor", children: [_jsx("div", { className: "rules-list", children: stateRef.current.rules.map((rule, idx) => {
|
|
160
|
+
const ruleColumns = buildColumnsForQueryBuilder(qbRefs.current[idx]);
|
|
161
|
+
return (_jsxs("div", { className: "rule-item", children: [_jsxs("div", { className: "rule-header", tabIndex: 0, id: `condition-header-${idx}`, "aria-expanded": stateRef.current.expandedRuleId === idx, "aria-labelledby": `condition-panel-${idx}`, "aria-label": `${l10n.getConstant('condition')} ${idx + 1}`, onClick: () => {
|
|
162
|
+
stateRef.current.expandedRuleId =
|
|
163
|
+
stateRef.current.expandedRuleId === idx ? null : idx;
|
|
164
|
+
forceRender({});
|
|
165
|
+
}, onKeyDown: (e) => {
|
|
166
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
167
|
+
e.preventDefault();
|
|
168
|
+
stateRef.current.expandedRuleId =
|
|
169
|
+
stateRef.current.expandedRuleId === idx ? null : idx;
|
|
170
|
+
forceRender({});
|
|
171
|
+
}
|
|
172
|
+
}, children: [_jsx("h4", { className: "rule-title", children: l10n.getConstant('condition') + ` ${idx + 1}` }), _jsx(ButtonComponent, { onClick: (e) => {
|
|
173
|
+
e.stopPropagation();
|
|
174
|
+
handleDeleteRule(idx);
|
|
175
|
+
}, cssClass: "e-flat", iconCss: "e-icons e-trash", type: "button", title: l10n.getConstant('delete'), "aria-label": `${l10n.getConstant('delete')} ${l10n.getConstant('condition')} ${idx + 1}` })] }), stateRef.current.expandedRuleId === idx && (_jsxs("div", { className: "rule-details", id: `condition-panel-${idx}`, role: "region", "aria-labelledby": `condition-header-${idx}`, children: [_jsx(AccordionWrapper, { title: l10n.getConstant('condition'), children: _jsxs("div", { className: "form-group", role: "group", "aria-label": l10n.getConstant('condition'), children: [_jsx("p", { className: "property-description", id: `condition-description-${idx}`, children: l10n.getConstant('conditionalDataConditionDescription') }), _jsx("div", { onBlur: () => { }, tabIndex: -1, "aria-describedby": `condition-description-${idx}`, "aria-label": l10n.getConstant('condition'), children: _jsx(QueryBuilderComponent, { ref: qbRefs.current[idx], dataSource: [], columns: ruleColumns, rule: rule.condition, ruleChange: () => handleRuleConditionChange(idx) }) })] }) }), _jsx("div", { className: "form-group", role: "group", "aria-label": l10n.getConstant('dataSource'), children: _jsx(DataSourceCollector, { datakey: 'Conditionaldata', value: rule.dataSource, onChange: (dataSource) => handleDataSourceChange(idx, dataSource), type: "grid", isAccordion: true }) }), _jsx("div", { className: "form-group", role: "group", "aria-label": l10n.getConstant('columns'), children: _jsx(ColumnBuilder, { columns: rule.columns || [], onChange: (columns) => handleColumnsChange(idx, columns), isConditionData: true, label: 'columns' }) })] }, 'Condionaldata'))] }, idx));
|
|
176
|
+
}) }), _jsx("div", { className: "rule-actions", role: "group", "aria-label": l10n.getConstant('conditionActions'), children: _jsx(ButtonComponent, { cssClass: "e-outline", onClick: handleAddRule, type: "button", "aria-label": l10n.getConstant('addCondition'), children: l10n.getConstant('addCondition') }) })] }));
|
|
177
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConditionalEditorUtils - Shared utility functions for Conditional and ConditionalData editors
|
|
3
|
+
* Exports field type inference, column templates, and helper functions
|
|
4
|
+
*/
|
|
5
|
+
import type { QueryBuilderComponent } from '@syncfusion/ej2-react-querybuilder';
|
|
6
|
+
import type { FormComponent } from '../types/FormSchema';
|
|
7
|
+
/**
|
|
8
|
+
* Infer field type from component type for QueryBuilder
|
|
9
|
+
*/
|
|
10
|
+
export declare function inferFieldType(component: FormComponent): 'string' | 'number' | 'date' | 'boolean' | 'time';
|
|
11
|
+
/**
|
|
12
|
+
* Check if component is an array field type
|
|
13
|
+
*/
|
|
14
|
+
export declare function isArrayField(component: FormComponent): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Check if component has selectable options
|
|
17
|
+
*/
|
|
18
|
+
export declare function hasSelectableOptions(component: FormComponent): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Create column template for QueryBuilder with custom input widgets
|
|
21
|
+
* Supports date, time, multiselect, dropdown, and other input types
|
|
22
|
+
*/
|
|
23
|
+
export declare function createColumnTemplate(component: FormComponent, qbRef: React.RefObject<QueryBuilderComponent | null>, fetchedOptions?: Record<string, any[]>): {
|
|
24
|
+
create: () => HTMLInputElement;
|
|
25
|
+
destroy: (args: any) => void;
|
|
26
|
+
write: (args: any) => void;
|
|
27
|
+
};
|