@syncfusion/ej2-react-form-builder 0.1.0-beta → 35.1.37
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/README.md +81 -36
- package/diConfig.json +148 -0
- package/dist/ej2-react-form-builder.min.js +10 -0
- package/dist/ej2-react-form-builder.umd.min.js +11 -0
- package/dist/ej2-react-form-builder.umd.min.js.map +1 -0
- package/dist/es6/ej2-react-form-builder.es2015.js +60 -0
- package/dist/es6/ej2-react-form-builder.es2015.js.map +1 -0
- package/form-builder.d.ts +4 -0
- package/form-builder.js +4 -0
- package/index.d.ts +2 -4
- package/index.js +2 -4
- package/license +6 -13
- package/package.json +19 -43
- package/src/form-builder/formbuilder.component.d.ts +37 -0
- package/src/form-builder/formbuilder.component.js +50 -0
- package/src/form-builder/index.d.ts +2 -0
- package/src/form-builder/index.js +2 -0
- package/src/form-builder/toolboxitems-directive.d.ts +28 -0
- package/src/form-builder/toolboxitems-directive.js +47 -0
- package/src/global.js +1 -0
- package/src/index.d.ts +2 -16
- package/src/index.js +2 -6
- package/Common/CheckboxGroup.d.ts +0 -13
- package/Common/CheckboxGroup.js +0 -50
- package/Common/ConditionalRuleEngine.d.ts +0 -296
- package/Common/ConditionalRuleEngine.js +0 -566
- package/Common/CustomTabComponent.d.ts +0 -17
- package/Common/CustomTabComponent.js +0 -30
- package/Common/CustomValidationEngine.d.ts +0 -58
- package/Common/CustomValidationEngine.js +0 -146
- package/Common/DataGridComponent.d.ts +0 -20
- package/Common/DataGridComponent.js +0 -273
- package/Common/FormValidator.d.ts +0 -485
- package/Common/FormValidator.js +0 -575
- package/Common/MinMaxRuleEngine.d.ts +0 -105
- package/Common/MinMaxRuleEngine.js +0 -365
- package/Common/StaticHtmlRenderer.d.ts +0 -14
- package/Common/StaticHtmlRenderer.js +0 -24
- package/Common/expressions/DependencyTracker.d.ts +0 -30
- package/Common/expressions/DependencyTracker.js +0 -128
- package/Common/expressions/ExpressionEngine.d.ts +0 -95
- package/Common/expressions/ExpressionEngine.js +0 -506
- package/Common/expressions/ExpressionParser.d.ts +0 -41
- package/Common/expressions/ExpressionParser.js +0 -269
- package/Common/expressions/useGridExpressions.d.ts +0 -12
- package/Common/expressions/useGridExpressions.js +0 -71
- package/Common/globalFormId.d.ts +0 -26
- package/Common/globalFormId.js +0 -45
- package/Common/json-converter.d.ts +0 -444
- package/Common/json-converter.js +0 -1184
- package/Common/parser.d.ts +0 -98
- package/Common/parser.js +0 -753
- package/Common/utils.d.ts +0 -130
- package/Common/utils.js +0 -574
- package/src/FormBuilder.d.ts +0 -211
- package/src/FormBuilder.js +0 -1299
- package/src/components/Database/ConnectionForm.d.ts +0 -8
- package/src/components/Database/ConnectionForm.js +0 -28
- package/src/components/TopBar.d.ts +0 -10
- package/src/components/TopBar.js +0 -7
- package/src/components/icons/ComponentIcons.d.ts +0 -44
- package/src/components/icons/ComponentIcons.js +0 -40
- package/src/components/layout/FormFieldWrapper.d.ts +0 -41
- package/src/components/layout/FormFieldWrapper.js +0 -112
- package/src/components/layout/LeftPane.d.ts +0 -23
- package/src/components/layout/LeftPane.js +0 -310
- package/src/components/layout/ListBoxPane.d.ts +0 -24
- package/src/components/layout/ListBoxPane.js +0 -94
- package/src/components/layout/MainSection.d.ts +0 -39
- package/src/components/layout/MainSection.js +0 -1766
- package/src/components/layout/Preview.d.ts +0 -12
- package/src/components/layout/Preview.js +0 -337
- package/src/components/layout/RightPane.d.ts +0 -22
- package/src/components/layout/RightPane.js +0 -370
- package/src/components/theme/ThemeSwitcher.d.ts +0 -14
- package/src/components/theme/ThemeSwitcher.js +0 -24
- package/src/components/theme/theme-constant.d.ts +0 -37
- package/src/components/theme/theme-constant.js +0 -23
- package/src/config/component-properties.json +0 -4929
- package/src/config/form-setting.json +0 -56
- package/src/config/locale.d.ts +0 -449
- package/src/config/locale.js +0 -449
- package/src/context/FormSchemaContext.d.ts +0 -32
- package/src/context/FormSchemaContext.js +0 -155
- package/src/context/LocalizationContext.d.ts +0 -27
- package/src/context/LocalizationContext.js +0 -43
- package/src/context/ThemeContext.d.ts +0 -23
- package/src/context/ThemeContext.js +0 -161
- package/src/context/contexts.d.ts +0 -6
- package/src/context/contexts.js +0 -6
- package/src/context/defaultFormSchema.d.ts +0 -6
- package/src/context/defaultFormSchema.js +0 -20
- package/src/defaultPalette.d.ts +0 -9
- package/src/defaultPalette.js +0 -45
- package/src/defaultTemplate.d.ts +0 -3
- package/src/defaultTemplate.js +0 -185
- package/src/import/schemaImportUtils.d.ts +0 -24
- package/src/import/schemaImportUtils.js +0 -121
- package/src/types/ActionButtons.d.ts +0 -44
- package/src/types/ActionButtons.js +0 -27
- package/src/types/ComponentProperties.d.ts +0 -33
- package/src/types/ComponentProperties.js +0 -2
- package/src/types/FormSchema.d.ts +0 -585
- package/src/types/FormSchema.js +0 -147
- package/src/types/connection.d.ts +0 -19
- package/src/types/connection.js +0 -9
- package/src/utils/AttributesEditor.d.ts +0 -13
- package/src/utils/AttributesEditor.js +0 -117
- package/src/utils/ButtonGroupEditor.d.ts +0 -15
- package/src/utils/ButtonGroupEditor.js +0 -141
- package/src/utils/ChoiceBasedFieldEditor.d.ts +0 -16
- package/src/utils/ChoiceBasedFieldEditor.js +0 -87
- package/src/utils/ClipboardHelper.d.ts +0 -26
- package/src/utils/ClipboardHelper.js +0 -83
- package/src/utils/ColumnBuilder.d.ts +0 -11
- package/src/utils/ColumnBuilder.js +0 -153
- package/src/utils/ColumnSpanEditor.d.ts +0 -8
- package/src/utils/ColumnSpanEditor.js +0 -98
- package/src/utils/ConditionDataEditor.d.ts +0 -29
- package/src/utils/ConditionDataEditor.js +0 -177
- package/src/utils/ConditionalEditorUtils.d.ts +0 -27
- package/src/utils/ConditionalEditorUtils.js +0 -310
- package/src/utils/ConditionsEditor.d.ts +0 -20
- package/src/utils/ConditionsEditor.js +0 -571
- package/src/utils/CustomAccordion.d.ts +0 -13
- package/src/utils/CustomAccordion.js +0 -38
- package/src/utils/CustomValidationEditor.d.ts +0 -11
- package/src/utils/CustomValidationEditor.js +0 -208
- package/src/utils/DataSourceCollector.d.ts +0 -3
- package/src/utils/DataSourceCollector.js +0 -252
- package/src/utils/ExpressionEditor.d.ts +0 -13
- package/src/utils/ExpressionEditor.js +0 -99
- package/src/utils/FormSettingsPanel.d.ts +0 -9
- package/src/utils/FormSettingsPanel.js +0 -60
- package/src/utils/LookupConfig.d.ts +0 -11
- package/src/utils/LookupConfig.js +0 -159
- package/src/utils/MinMaxValueEditor.d.ts +0 -25
- package/src/utils/MinMaxValueEditor.js +0 -188
- package/src/utils/OptionsEditor.d.ts +0 -8
- package/src/utils/OptionsEditor.js +0 -79
- package/src/utils/PropertyRenderer.d.ts +0 -22
- package/src/utils/PropertyRenderer.js +0 -210
- package/src/utils/ValueRenderer.d.ts +0 -2
- package/src/utils/ValueRenderer.js +0 -91
- package/src/utils/ej2DbAdaptor.d.ts +0 -6
- package/src/utils/ej2DbAdaptor.js +0 -38
- package/src/utils/mappingHeuristics.d.ts +0 -10
- package/src/utils/mappingHeuristics.js +0 -33
- package/src/utils/renderComponent.d.ts +0 -18
- package/src/utils/renderComponent.js +0 -300
- package/styles/tailwind3.css +0 -67
- package/styles/themes/action-buttons.css +0 -110
- package/styles/themes/datagrid.css +0 -380
- package/styles/themes/drag-reorder.css +0 -248
- package/styles/themes/import-dialog.css +0 -292
- package/styles/themes/index.css +0 -658
- package/styles/themes/left-pane.css +0 -253
- package/styles/themes/options-editor.css +0 -63
- package/styles/themes/preview.css +0 -702
- package/styles/themes/property-dialog.css +0 -371
|
@@ -1,1766 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { enableRipple } from '@syncfusion/ej2-base';
|
|
3
|
-
import { useState, useRef, useEffect, useImperativeHandle, useCallback } from 'react';
|
|
4
|
-
import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
|
|
5
|
-
import { Tooltip, TooltipComponent } from '@syncfusion/ej2-react-popups';
|
|
6
|
-
import { Form, FormField } from '../../../Common/FormValidator';
|
|
7
|
-
import { VALIDATION_REGEX } from '../../../Common/utils';
|
|
8
|
-
import { Draggable, Droppable } from '@syncfusion/react-base';
|
|
9
|
-
import { useFormSchema, useLocalization } from '../../context/contexts';
|
|
10
|
-
import { RenderComponent } from '../../utils/renderComponent';
|
|
11
|
-
import { FormFieldWrapper } from './FormFieldWrapper';
|
|
12
|
-
import { Preview } from './Preview';
|
|
13
|
-
import { CustomTabComponent } from '../../../Common/CustomTabComponent';
|
|
14
|
-
import { StaticHtmlRenderer } from '../../../Common/StaticHtmlRenderer';
|
|
15
|
-
import { generateId, generateName, splitSchema } from '../../../Common/json-converter';
|
|
16
|
-
import React from 'react';
|
|
17
|
-
import ListBoxPane from './ListBoxPane';
|
|
18
|
-
import { splitWidths } from '../../../Common/utils';
|
|
19
|
-
import { extractTemplateComponents } from '../../FormBuilder';
|
|
20
|
-
import { ItemDirective, ItemsDirective, ToolbarComponent } from '@syncfusion/ej2-react-navigations';
|
|
21
|
-
import { defaultFormSchema } from '../../context/defaultFormSchema';
|
|
22
|
-
import { MessageComponent } from '@syncfusion/ej2-react-notifications';
|
|
23
|
-
// import { builderSubmitUrl } from '../../../tempConstants';
|
|
24
|
-
import { setFormId, getFormId } from '../../../Common/globalFormId';
|
|
25
|
-
import { FormDropZoneIcon } from '../icons/ComponentIcons';
|
|
26
|
-
export const isDefaultFormSchema = (schema) => {
|
|
27
|
-
return schema.components.length === 0 || schema.components.length === 1 &&
|
|
28
|
-
schema.components[0]?.type === 'button' &&
|
|
29
|
-
schema.components[0]?.buttonType === 'submit';
|
|
30
|
-
};
|
|
31
|
-
const MainSection = React.forwardRef(({ formSchema, activeFieldId, onDrop, onNestedDrop, onReorder, onNestedReorder, onDelete, onEdit, onCopy, onPaste, onSwitchWidget, enablePreview, mode, onFieldDragStarted, onFieldDraggingOver, className, onFieldDropped, paletteConfig, hasClipboardData }, ref) => {
|
|
32
|
-
// Enable ripple effect for Syncfusion components
|
|
33
|
-
enableRipple(true);
|
|
34
|
-
// Track initial drag position and current direction
|
|
35
|
-
const initialDragYRef = useRef(null);
|
|
36
|
-
const currentDragYRef = useRef(null);
|
|
37
|
-
const nestedInitialDragYRef = useRef(null);
|
|
38
|
-
const nestedCurrentDragYRef = useRef(null);
|
|
39
|
-
const reOrdering = useRef(false);
|
|
40
|
-
const nestedReOrdering = useRef(false);
|
|
41
|
-
// Track drop position for palette drag (drop-between-fields)
|
|
42
|
-
const dropTargetIndexRef = useRef(null);
|
|
43
|
-
const dropPositionRef = useRef(null);
|
|
44
|
-
const nestedDropTargetIndexRef = useRef(null);
|
|
45
|
-
const nestedDropPositionRef = useRef(null);
|
|
46
|
-
const isProcessingNestedDropRef = useRef(false);
|
|
47
|
-
const [isDraggingOver, setIsDraggingOver] = useState(false);
|
|
48
|
-
const [showSplitView, setShowSplitView] = useState(false);
|
|
49
|
-
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
|
50
|
-
const [draggingOverPanel, setDraggingOverPanel] = useState(null);
|
|
51
|
-
const [formState, setFormState] = useState();
|
|
52
|
-
const [isListBoxPaneOpen, setIsListBoxPaneOpen] = useState(false);
|
|
53
|
-
const [currentMode, setCurrentMode] = useState(mode || 'Developer');
|
|
54
|
-
const { l10n, locale } = useLocalization();
|
|
55
|
-
// Access FormSchema context for import functionality
|
|
56
|
-
const { setFormSchema } = useFormSchema();
|
|
57
|
-
// Drag and reorder state
|
|
58
|
-
const draggedItemIndex = useRef(null);
|
|
59
|
-
const [showClone, setShowClone] = useState(false);
|
|
60
|
-
const cloneHelperRef = useRef(null);
|
|
61
|
-
const currentComponentsRef = useRef(formSchema.components);
|
|
62
|
-
const [isReordering, setIsReordering] = useState(false);
|
|
63
|
-
// Nested reorder state
|
|
64
|
-
const [isNestedReordering, setIsNestedReordering] = useState(false);
|
|
65
|
-
const [activeNestedContainer, setActiveNestedContainer] = useState(null);
|
|
66
|
-
const nestedDraggedIndex = useRef(null);
|
|
67
|
-
const nestedCloneHelperRef = useRef(null);
|
|
68
|
-
const nestedComponentsRef = useRef([]);
|
|
69
|
-
const nestedContainerIdRef = useRef(null);
|
|
70
|
-
const nestedContainerPathRef = useRef(null);
|
|
71
|
-
const nestedCellInfoRef = useRef(undefined);
|
|
72
|
-
// dataGrid refs management - maintains refs to all grid components for data collection
|
|
73
|
-
const gridRefsRef = useRef({});
|
|
74
|
-
/**
|
|
75
|
-
* Recursively collect all components including nested ones
|
|
76
|
-
*/
|
|
77
|
-
const collectAllComponents = useCallback((components) => {
|
|
78
|
-
const allComponents = [];
|
|
79
|
-
const traverse = (comps) => {
|
|
80
|
-
comps.forEach(comp => {
|
|
81
|
-
allComponents.push(comp);
|
|
82
|
-
// Handle nested components in panels
|
|
83
|
-
if (comp.children && comp.children.length > 0) {
|
|
84
|
-
traverse(comp.children);
|
|
85
|
-
}
|
|
86
|
-
// Handle components in table cells
|
|
87
|
-
if (comp.tableCells) {
|
|
88
|
-
comp.tableCells.forEach(row => {
|
|
89
|
-
row.forEach(cell => {
|
|
90
|
-
traverse(cell);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
// Handle components in tabs
|
|
95
|
-
if (comp.tabItems) {
|
|
96
|
-
comp.tabItems.forEach(tab => {
|
|
97
|
-
if (tab.content) {
|
|
98
|
-
traverse(tab.content);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
};
|
|
104
|
-
traverse(components);
|
|
105
|
-
return allComponents;
|
|
106
|
-
}, []);
|
|
107
|
-
const initialValues = React.useMemo(() => {
|
|
108
|
-
const values = {};
|
|
109
|
-
const allComponents = collectAllComponents(formSchema.components);
|
|
110
|
-
const componentIds = new Set();
|
|
111
|
-
allComponents.forEach(comp => {
|
|
112
|
-
// Do NOT store dataGrid data inside the form state; grids manage their own internal data.
|
|
113
|
-
if (comp.type === 'dataGrid') {
|
|
114
|
-
componentIds.add(comp.id);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
if (comp.defaultValue !== undefined) {
|
|
118
|
-
values[comp.id] = comp.defaultValue;
|
|
119
|
-
}
|
|
120
|
-
componentIds.add(comp.id);
|
|
121
|
-
});
|
|
122
|
-
if (formState) {
|
|
123
|
-
Object.keys(formState.values).filter(key => !componentIds.has(key)).forEach(key => {
|
|
124
|
-
delete formState.values[key];
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
return values;
|
|
128
|
-
}, [formSchema, collectAllComponents]);
|
|
129
|
-
// Update the useEffect that sets default values
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
if (formState && initialValues) {
|
|
132
|
-
Object.keys(formState.values).filter(key => !(key in initialValues));
|
|
133
|
-
Object.entries(initialValues).forEach(([id, value]) => {
|
|
134
|
-
if (!formState.values[id]) {
|
|
135
|
-
formState.values[id] = value;
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
}, [formState, formSchema, initialValues]);
|
|
140
|
-
// Expose gridRefs via forwardRef for parent components to collect grid data on form submission
|
|
141
|
-
useImperativeHandle(ref, () => ({
|
|
142
|
-
getGridRefs: () => gridRefsRef.current,
|
|
143
|
-
}), []);
|
|
144
|
-
// Cross-container move tracking
|
|
145
|
-
const [draggedComponentInfo, setDraggedComponentInfo] = useState(null);
|
|
146
|
-
// Track whether we're currently dragging from palette
|
|
147
|
-
const isDraggingFromPaletteRef = useRef(false);
|
|
148
|
-
const [activeTabIndices, setActiveTabIndices] = useState({});
|
|
149
|
-
useEffect(() => {
|
|
150
|
-
currentComponentsRef.current = formSchema.components;
|
|
151
|
-
}, [formSchema.components]);
|
|
152
|
-
useEffect(() => {
|
|
153
|
-
let tooltip = new Tooltip({
|
|
154
|
-
target: '#toolbar [title]',
|
|
155
|
-
position: 'BottomCenter',
|
|
156
|
-
});
|
|
157
|
-
tooltip.appendTo('#Editor-Tooltip');
|
|
158
|
-
return () => { tooltip.destroy(); };
|
|
159
|
-
}, [showSplitView, isPreviewOpen, formSchema]);
|
|
160
|
-
useEffect(() => {
|
|
161
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
162
|
-
const urlMode = urlParams.get('mode');
|
|
163
|
-
if (urlMode === 'developer' || urlMode === 'simple') {
|
|
164
|
-
const capitalizedMode = urlMode.charAt(0).toUpperCase() + urlMode.slice(1);
|
|
165
|
-
setCurrentMode(capitalizedMode);
|
|
166
|
-
}
|
|
167
|
-
}, []);
|
|
168
|
-
// Build validation rules from form schema
|
|
169
|
-
const buildValidationRules = useCallback(() => {
|
|
170
|
-
const rules = {};
|
|
171
|
-
const collectRules = (components) => {
|
|
172
|
-
components.forEach((component) => {
|
|
173
|
-
if (component) {
|
|
174
|
-
const componentRules = {};
|
|
175
|
-
if (component.required && !component.disabled && !component.readOnly) {
|
|
176
|
-
componentRules.required = [true, component.validationMessage || `${component.label} is required`];
|
|
177
|
-
}
|
|
178
|
-
if (component.minLength && !component.disabled && !component.readOnly) {
|
|
179
|
-
componentRules.minLength = [component.minLength, `Minimum ${component.minLength} characters required`];
|
|
180
|
-
}
|
|
181
|
-
if (component.maxLength && !component.disabled && !component.readOnly) {
|
|
182
|
-
componentRules.maxLength = [component.maxLength, `Maximum ${component.maxLength} characters allowed`];
|
|
183
|
-
}
|
|
184
|
-
if (component?.type === 'textbox' && component?.textboxType && !component?.regex && !componentRules.regex && !component.disabled && !component.readOnly) {
|
|
185
|
-
if (component.textboxType === 'email') {
|
|
186
|
-
componentRules.regex = [VALIDATION_REGEX['EMAIL'], component?.validationMessage || 'Invalid format'];
|
|
187
|
-
}
|
|
188
|
-
else if (component.textboxType === 'url') {
|
|
189
|
-
componentRules.regex = [VALIDATION_REGEX['URL'], component?.validationMessage || 'Invalid format'];
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (component.regex && !component.disabled && !component.readOnly) {
|
|
193
|
-
componentRules.regex = [component.regex, component.validationMessage || 'Invalid format'];
|
|
194
|
-
}
|
|
195
|
-
if (Object.keys(componentRules).length > 0) {
|
|
196
|
-
rules[component.id] = componentRules;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
// Recursively collect rules from nested components
|
|
200
|
-
if (component.children) {
|
|
201
|
-
collectRules(component.children);
|
|
202
|
-
}
|
|
203
|
-
if (component.tableCells) {
|
|
204
|
-
component.tableCells.forEach(row => {
|
|
205
|
-
row.forEach(cell => collectRules(cell));
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
if (component.tabItems) {
|
|
209
|
-
component.tabItems.forEach(tab => {
|
|
210
|
-
if (tab.content)
|
|
211
|
-
collectRules(tab.content);
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
};
|
|
216
|
-
collectRules(formSchema.components);
|
|
217
|
-
return rules;
|
|
218
|
-
}, [formSchema]);
|
|
219
|
-
const validationRules = buildValidationRules();
|
|
220
|
-
// Helper: Find component by ID recursively in the entire form schema
|
|
221
|
-
const findComponentById = (componentId) => {
|
|
222
|
-
const searchInComponents = (components) => {
|
|
223
|
-
for (const comp of components) {
|
|
224
|
-
if (comp.id === componentId)
|
|
225
|
-
return comp;
|
|
226
|
-
// Search in panel children
|
|
227
|
-
if (comp.children) {
|
|
228
|
-
const found = searchInComponents(comp.children);
|
|
229
|
-
if (found)
|
|
230
|
-
return found;
|
|
231
|
-
}
|
|
232
|
-
// Search in table cells
|
|
233
|
-
if (comp.tableCells) {
|
|
234
|
-
for (const row of comp.tableCells) {
|
|
235
|
-
for (const cell of row) {
|
|
236
|
-
const found = searchInComponents(cell);
|
|
237
|
-
if (found)
|
|
238
|
-
return found;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
// Search in tab items
|
|
243
|
-
if (comp.tabItems) {
|
|
244
|
-
for (const tab of comp.tabItems) {
|
|
245
|
-
if (tab.content) {
|
|
246
|
-
const found = searchInComponents(tab.content);
|
|
247
|
-
if (found)
|
|
248
|
-
return found;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return null;
|
|
254
|
-
};
|
|
255
|
-
return searchInComponents(formSchema.components);
|
|
256
|
-
};
|
|
257
|
-
// Helper: check if an id exists inside a component's subtree
|
|
258
|
-
const isIdInSubtree = (root, idToFind) => {
|
|
259
|
-
if (!root)
|
|
260
|
-
return false;
|
|
261
|
-
const search = (comp) => {
|
|
262
|
-
if (comp.id === idToFind)
|
|
263
|
-
return true;
|
|
264
|
-
if (comp.children) {
|
|
265
|
-
for (const c of comp.children) {
|
|
266
|
-
if (search(c))
|
|
267
|
-
return true;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
if (comp.tableCells) {
|
|
271
|
-
for (const row of comp.tableCells) {
|
|
272
|
-
for (const cellArr of row) {
|
|
273
|
-
for (const c of cellArr) {
|
|
274
|
-
if (search(c))
|
|
275
|
-
return true;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
if (comp.tabItems) {
|
|
281
|
-
for (const tab of comp.tabItems) {
|
|
282
|
-
if (tab.content) {
|
|
283
|
-
for (const c of tab.content) {
|
|
284
|
-
if (search(c))
|
|
285
|
-
return true;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return false;
|
|
291
|
-
};
|
|
292
|
-
return search(root);
|
|
293
|
-
};
|
|
294
|
-
const findDropTarget = useCallback((cursorY, containerSelector, container) => {
|
|
295
|
-
const fields = container ? container.querySelectorAll(containerSelector) : document.querySelectorAll(containerSelector);
|
|
296
|
-
if (fields.length === 0)
|
|
297
|
-
return null;
|
|
298
|
-
// First, try to find field directly under cursor
|
|
299
|
-
for (let i = 0; i < fields.length; i++) {
|
|
300
|
-
const rect = fields[i].getBoundingClientRect();
|
|
301
|
-
if (cursorY >= rect.top && cursorY < rect.bottom) {
|
|
302
|
-
const midpoint = rect.top + rect.height / 2;
|
|
303
|
-
return {
|
|
304
|
-
index: i,
|
|
305
|
-
position: cursorY < midpoint ? 'before' : 'after'
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
for (let i = 0; i < fields.length - 1; i++) {
|
|
310
|
-
const currentRect = fields[i].getBoundingClientRect();
|
|
311
|
-
const nextRect = fields[i + 1].getBoundingClientRect();
|
|
312
|
-
// If cursor is in the gap between current and next field
|
|
313
|
-
if (cursorY > currentRect.bottom && cursorY < nextRect.top) {
|
|
314
|
-
// Drop after current field (before next field)
|
|
315
|
-
return {
|
|
316
|
-
index: i + 1,
|
|
317
|
-
position: 'before'
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
// Check if cursor is above first field
|
|
322
|
-
const firstRect = fields[0].getBoundingClientRect();
|
|
323
|
-
if (cursorY < firstRect.top) {
|
|
324
|
-
return {
|
|
325
|
-
index: 0,
|
|
326
|
-
position: 'before'
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
// Check if cursor is below last field
|
|
330
|
-
const lastRect = fields[fields.length - 1].getBoundingClientRect();
|
|
331
|
-
if (cursorY > lastRect.bottom) {
|
|
332
|
-
return {
|
|
333
|
-
index: fields.length - 1,
|
|
334
|
-
position: 'after'
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
return null;
|
|
338
|
-
}, []);
|
|
339
|
-
// Reorder drag handlers
|
|
340
|
-
const isValidTarget = (element) => {
|
|
341
|
-
return element && element.classList.contains('form-field-draggable');
|
|
342
|
-
};
|
|
343
|
-
const helper = (args) => {
|
|
344
|
-
const formField = (args.sender?.target).closest('.form-field-draggable');
|
|
345
|
-
if (!formField || !isValidTarget(formField)) {
|
|
346
|
-
return null;
|
|
347
|
-
}
|
|
348
|
-
const index = parseInt(formField.getAttribute('data-index') || '0', 10);
|
|
349
|
-
draggedItemIndex.current = index;
|
|
350
|
-
setShowClone(true);
|
|
351
|
-
return cloneHelperRef.current;
|
|
352
|
-
};
|
|
353
|
-
const dragStart = (e) => {
|
|
354
|
-
if (cloneHelperRef.current) {
|
|
355
|
-
cloneHelperRef.current.style.display = 'flex';
|
|
356
|
-
}
|
|
357
|
-
// CAPTURE INITIAL POSITION from mouse event
|
|
358
|
-
if (e && (e.event.clientY !== undefined || e.event.touches[0].clientY !== undefined)) {
|
|
359
|
-
initialDragYRef.current = e.event.clientY || e.event.touches[0].clientY;
|
|
360
|
-
currentDragYRef.current = e.event.clientY || e.event.touches[0].clientY;
|
|
361
|
-
}
|
|
362
|
-
// Capture what's being dragged from root
|
|
363
|
-
if (draggedItemIndex.current !== null) {
|
|
364
|
-
const component = currentComponentsRef.current[draggedItemIndex.current];
|
|
365
|
-
setDraggedComponentInfo({
|
|
366
|
-
id: component.id,
|
|
367
|
-
containerId: 'root'
|
|
368
|
-
});
|
|
369
|
-
// Add class to the actual DOM element being dragged
|
|
370
|
-
const draggedElement = document.querySelector(`[data-component-id="${component.id}"]`);
|
|
371
|
-
if (draggedElement) {
|
|
372
|
-
draggedElement.classList.add('is-being-dragged');
|
|
373
|
-
}
|
|
374
|
-
// Trigger onFieldDragStarted event
|
|
375
|
-
if (onFieldDragStarted) {
|
|
376
|
-
onFieldDragStarted({
|
|
377
|
-
componentName: component.name,
|
|
378
|
-
componentType: component.type,
|
|
379
|
-
sourceContainerId: 'root',
|
|
380
|
-
isNested: false
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
// NOT palette drag - it's internal reordering
|
|
385
|
-
isDraggingFromPaletteRef.current = false;
|
|
386
|
-
};
|
|
387
|
-
const drag = (e) => {
|
|
388
|
-
if (currentComponentsRef.current.length > 1) {
|
|
389
|
-
setIsReordering(true);
|
|
390
|
-
reOrdering.current = true;
|
|
391
|
-
}
|
|
392
|
-
if (cloneHelperRef.current) {
|
|
393
|
-
cloneHelperRef.current.style.opacity = '0.97';
|
|
394
|
-
// DETERMINE DIRECTION and add appropriate class
|
|
395
|
-
if (e && (e.event.clientY !== undefined || e.event.touches[0].clientY !== undefined) && initialDragYRef.current !== null) {
|
|
396
|
-
const currentY = e.event.clientY || e.event.touches[0].clientY;
|
|
397
|
-
const initialY = initialDragYRef.current;
|
|
398
|
-
if (e.event?.touches?.length > 0) {
|
|
399
|
-
const target = document.elementFromPoint(e.event.touches[0].clientX, e.event.touches[0].clientY);
|
|
400
|
-
const wrapper = target?.closest('.form-field-wrapper');
|
|
401
|
-
document.querySelectorAll('.form-field-wrapper.touch-hovered').forEach(el => {
|
|
402
|
-
if (el !== wrapper)
|
|
403
|
-
el.classList.remove('touch-hovered');
|
|
404
|
-
});
|
|
405
|
-
if (wrapper && !wrapper.classList.contains('touch-hovered')) {
|
|
406
|
-
wrapper.classList.add('touch-hovered');
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
// ALSO apply direction class to form-preview-area for styling hover targets
|
|
410
|
-
const formPreviewArea = document.querySelector('.form-preview-area');
|
|
411
|
-
if (formPreviewArea) {
|
|
412
|
-
formPreviewArea.classList.remove('drag-moving-up', 'drag-moving-down');
|
|
413
|
-
if (currentY < initialY) {
|
|
414
|
-
formPreviewArea.classList.add('drag-moving-up');
|
|
415
|
-
}
|
|
416
|
-
else if (currentY > initialY) {
|
|
417
|
-
formPreviewArea.classList.add('drag-moving-down');
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
// UPDATE current position ref for tracking
|
|
421
|
-
currentDragYRef.current = currentY;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
const dragStop = (args) => {
|
|
426
|
-
// Clear palette drag flag
|
|
427
|
-
isDraggingFromPaletteRef.current = false;
|
|
428
|
-
setIsReordering(false);
|
|
429
|
-
reOrdering.current = false;
|
|
430
|
-
setIsDraggingOver(false);
|
|
431
|
-
setDraggingOverPanel(null);
|
|
432
|
-
setShowClone(false);
|
|
433
|
-
// RESET drag position tracking on stop
|
|
434
|
-
initialDragYRef.current = null;
|
|
435
|
-
currentDragYRef.current = null;
|
|
436
|
-
const formPreviewArea = document.querySelector('.form-preview-area');
|
|
437
|
-
if (formPreviewArea) {
|
|
438
|
-
formPreviewArea.classList.remove('drag-moving-up', 'drag-moving-down');
|
|
439
|
-
}
|
|
440
|
-
if (draggedItemIndex.current === null)
|
|
441
|
-
return;
|
|
442
|
-
const component = currentComponentsRef.current[draggedItemIndex.current];
|
|
443
|
-
const target = args.target;
|
|
444
|
-
// Remove class from the dragged element
|
|
445
|
-
const draggedElement = document.querySelector(`[data-component-id="${component.id}"]`);
|
|
446
|
-
if (draggedElement) {
|
|
447
|
-
draggedElement.classList.remove('is-being-dragged');
|
|
448
|
-
}
|
|
449
|
-
// Check if dropped on a panel drop zone (cross-container move)
|
|
450
|
-
const panelDropZone = target?.closest('.panel-drop-zone');
|
|
451
|
-
if (panelDropZone) {
|
|
452
|
-
const panelId = panelDropZone.getAttribute('data-panel-id');
|
|
453
|
-
if (panelId && component) {
|
|
454
|
-
// Prevent dropping a component into itself or its own descendant
|
|
455
|
-
if (panelId === component.id || isIdInSubtree(component, panelId)) {
|
|
456
|
-
draggedItemIndex.current = null;
|
|
457
|
-
setDraggedComponentInfo(null);
|
|
458
|
-
isDraggingFromPaletteRef.current = false;
|
|
459
|
-
clearNestedDropTargetVisuals();
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
// Move from root to panel - DELETE FIRST, then ADD
|
|
463
|
-
onDelete(component.id, true);
|
|
464
|
-
setTimeout(() => {
|
|
465
|
-
onNestedDrop(panelId, component, undefined, panelId, nestedDropTargetIndexRef.current, nestedDropPositionRef.current);
|
|
466
|
-
}, 0);
|
|
467
|
-
isDraggingFromPaletteRef.current = false;
|
|
468
|
-
clearNestedDropTargetVisuals();
|
|
469
|
-
draggedItemIndex.current = null;
|
|
470
|
-
setDraggedComponentInfo(null);
|
|
471
|
-
if (onFieldDropped) {
|
|
472
|
-
onFieldDropped({
|
|
473
|
-
componentName: component.name,
|
|
474
|
-
componentType: component.type,
|
|
475
|
-
sourceContainerId: 'root',
|
|
476
|
-
targetContainerId: panelId,
|
|
477
|
-
isReordered: false
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
// Check if dropped on a tab drop zone (cross-container move)
|
|
484
|
-
const tabDropZone = target?.closest('.tab-drop-zone');
|
|
485
|
-
if (tabDropZone) {
|
|
486
|
-
const tabContainerId = tabDropZone.getAttribute('data-container-id');
|
|
487
|
-
const tabIndex = parseInt(tabDropZone.getAttribute('data-tab-index') || '0', 10);
|
|
488
|
-
if (tabContainerId && component) {
|
|
489
|
-
// Prevent dropping into same component or into its descendant container
|
|
490
|
-
if (tabContainerId === component.id || isIdInSubtree(component, tabContainerId)) {
|
|
491
|
-
draggedItemIndex.current = null;
|
|
492
|
-
setDraggedComponentInfo(null);
|
|
493
|
-
isDraggingFromPaletteRef.current = false;
|
|
494
|
-
clearNestedDropTargetVisuals();
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
497
|
-
// Move from root to tab
|
|
498
|
-
onDelete(component.id, true);
|
|
499
|
-
setTimeout(() => {
|
|
500
|
-
onNestedDrop(tabContainerId, component, { row: 0, col: 0, tabIndex }, tabContainerId, nestedDropTargetIndexRef.current, nestedDropPositionRef.current);
|
|
501
|
-
}, 0);
|
|
502
|
-
isDraggingFromPaletteRef.current = false;
|
|
503
|
-
clearNestedDropTargetVisuals();
|
|
504
|
-
draggedItemIndex.current = null;
|
|
505
|
-
setDraggedComponentInfo(null);
|
|
506
|
-
if (onFieldDropped) {
|
|
507
|
-
onFieldDropped({
|
|
508
|
-
componentName: component.name,
|
|
509
|
-
componentType: component.type,
|
|
510
|
-
sourceContainerId: 'root',
|
|
511
|
-
targetContainerId: tabContainerId,
|
|
512
|
-
isReordered: false
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
// Check if dropped on a table cell (cross-container move)
|
|
519
|
-
const tableCell = target?.closest('.table-cell');
|
|
520
|
-
if (tableCell) {
|
|
521
|
-
const cellContainerId = tableCell.getAttribute('data-container-id');
|
|
522
|
-
const cellRow = parseInt(tableCell.getAttribute('data-cell-row') || '0', 10);
|
|
523
|
-
const cellCol = parseInt(tableCell.getAttribute('data-cell-col') || '0', 10);
|
|
524
|
-
if (cellContainerId && component) {
|
|
525
|
-
// Prevent dropping into same component or into its descendant container
|
|
526
|
-
if (cellContainerId === component.id || isIdInSubtree(component, cellContainerId)) {
|
|
527
|
-
draggedItemIndex.current = null;
|
|
528
|
-
setDraggedComponentInfo(null);
|
|
529
|
-
isDraggingFromPaletteRef.current = false;
|
|
530
|
-
clearNestedDropTargetVisuals();
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
// Move from root to table cell
|
|
534
|
-
onDelete(component.id, true);
|
|
535
|
-
setTimeout(() => {
|
|
536
|
-
onNestedDrop(cellContainerId, component, { row: cellRow, col: cellCol }, cellContainerId, nestedDropTargetIndexRef.current, nestedDropPositionRef.current);
|
|
537
|
-
}, 0);
|
|
538
|
-
isDraggingFromPaletteRef.current = false;
|
|
539
|
-
clearNestedDropTargetVisuals();
|
|
540
|
-
draggedItemIndex.current = null;
|
|
541
|
-
setDraggedComponentInfo(null);
|
|
542
|
-
if (onFieldDropped) {
|
|
543
|
-
onFieldDropped({
|
|
544
|
-
componentName: component.name,
|
|
545
|
-
componentType: component.type,
|
|
546
|
-
sourceContainerId: 'root',
|
|
547
|
-
targetContainerId: cellContainerId,
|
|
548
|
-
isReordered: false
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
// Same container reorder (existing logic)
|
|
555
|
-
const targetElement = args.target?.closest('.form-field-draggable');
|
|
556
|
-
if (targetElement) {
|
|
557
|
-
const targetIndex = parseInt(targetElement.getAttribute('data-index') || '0', 10);
|
|
558
|
-
if (targetIndex !== -1 && targetIndex !== draggedItemIndex.current) {
|
|
559
|
-
const newComponents = [...currentComponentsRef.current];
|
|
560
|
-
const draggedComponent = newComponents[draggedItemIndex.current];
|
|
561
|
-
newComponents.splice(draggedItemIndex.current, 1);
|
|
562
|
-
newComponents.splice(targetIndex, 0, draggedComponent);
|
|
563
|
-
onReorder(newComponents);
|
|
564
|
-
// Trigger onFieldDropped event for reordering
|
|
565
|
-
if (onFieldDropped) {
|
|
566
|
-
onFieldDropped({
|
|
567
|
-
componentName: draggedComponent.name,
|
|
568
|
-
componentType: draggedComponent.type,
|
|
569
|
-
sourceContainerId: 'root',
|
|
570
|
-
targetContainerId: 'root',
|
|
571
|
-
isReordered: true
|
|
572
|
-
});
|
|
573
|
-
isDraggingFromPaletteRef.current = false;
|
|
574
|
-
clearDropTargetVisuals();
|
|
575
|
-
clearNestedDropTargetVisuals();
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
draggedItemIndex.current = null;
|
|
580
|
-
isDraggingFromPaletteRef.current = false;
|
|
581
|
-
clearDropTargetVisuals();
|
|
582
|
-
clearNestedDropTargetVisuals();
|
|
583
|
-
setDraggedComponentInfo(null); // Clear tracking
|
|
584
|
-
};
|
|
585
|
-
const handleDragOver = (args) => {
|
|
586
|
-
if (isReordering || isNestedReordering || reOrdering.current) {
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
setIsDraggingOver(true);
|
|
590
|
-
// Check if this is a palette drag by looking for palette clone
|
|
591
|
-
const paletteClone = document.querySelector('.palette-item-clone');
|
|
592
|
-
if (paletteClone) {
|
|
593
|
-
// Activate continuous position tracking for palette drag
|
|
594
|
-
isDraggingFromPaletteRef.current = true;
|
|
595
|
-
}
|
|
596
|
-
// DETECT DROP POSITION during palette drag - CONTINUOUSLY based on current mouse position
|
|
597
|
-
if (args && args.event && args.event.clientY !== undefined && !reOrdering.current) {
|
|
598
|
-
const result = findDropTarget(args.event.clientY, '.form-field-draggable');
|
|
599
|
-
if (result) {
|
|
600
|
-
// Update refs immediately
|
|
601
|
-
dropTargetIndexRef.current = result.index;
|
|
602
|
-
dropPositionRef.current = result.position;
|
|
603
|
-
// Immediately apply visual classes to indicate drop position
|
|
604
|
-
updateDropTargetVisuals(result);
|
|
605
|
-
}
|
|
606
|
-
else {
|
|
607
|
-
// Clear drop target if not over any field
|
|
608
|
-
dropTargetIndexRef.current = null;
|
|
609
|
-
dropPositionRef.current = null;
|
|
610
|
-
clearDropTargetVisuals();
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
// Trigger onFieldDraggingOver event
|
|
614
|
-
if (onFieldDraggingOver) {
|
|
615
|
-
onFieldDraggingOver({
|
|
616
|
-
isNested: false
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
};
|
|
620
|
-
// Helper to apply drop target visual feedback
|
|
621
|
-
const updateDropTargetVisuals = (target) => {
|
|
622
|
-
const fields = document.querySelectorAll('.form-field-draggable');
|
|
623
|
-
fields.forEach((field, idx) => {
|
|
624
|
-
field.classList.remove('drop-target-before', 'drop-target-after');
|
|
625
|
-
if (idx === target.index) {
|
|
626
|
-
field.classList.add(`drop-target-${target.position}`);
|
|
627
|
-
}
|
|
628
|
-
});
|
|
629
|
-
};
|
|
630
|
-
// Helper to clear drop target visual feedback
|
|
631
|
-
const clearDropTargetVisuals = () => {
|
|
632
|
-
const fields = document.querySelectorAll('.form-field-draggable');
|
|
633
|
-
fields.forEach(field => {
|
|
634
|
-
field.classList.remove('drop-target-before', 'drop-target-after');
|
|
635
|
-
});
|
|
636
|
-
};
|
|
637
|
-
const handleDragLeave = () => {
|
|
638
|
-
// Stop continuous tracking when leaving drop zone
|
|
639
|
-
isDraggingFromPaletteRef.current = false;
|
|
640
|
-
setIsDraggingOver(false);
|
|
641
|
-
// Clear drop target visual feedback
|
|
642
|
-
dropTargetIndexRef.current = null;
|
|
643
|
-
dropPositionRef.current = null;
|
|
644
|
-
clearDropTargetVisuals();
|
|
645
|
-
};
|
|
646
|
-
const handleDrop = () => {
|
|
647
|
-
// Clear palette drag flag
|
|
648
|
-
isDraggingFromPaletteRef.current = false;
|
|
649
|
-
setIsDraggingOver(false);
|
|
650
|
-
// Clear drop target visual feedback
|
|
651
|
-
clearDropTargetVisuals();
|
|
652
|
-
// Check if this is an internal move from panel to root
|
|
653
|
-
if (draggedComponentInfo && draggedComponentInfo.containerId !== 'root') {
|
|
654
|
-
const component = findComponentById(draggedComponentInfo.id);
|
|
655
|
-
if (component) {
|
|
656
|
-
onDrop(component);
|
|
657
|
-
onDelete(component.id, true);
|
|
658
|
-
setDraggedComponentInfo(null);
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
// Check for template drop first (data-template-json)
|
|
663
|
-
let cloneElement = document.querySelector('.palette-item-clone[data-template-json]');
|
|
664
|
-
if (cloneElement) {
|
|
665
|
-
const templateData = cloneElement.getAttribute('data-template-json');
|
|
666
|
-
if (templateData) {
|
|
667
|
-
try {
|
|
668
|
-
const template = JSON.parse(templateData);
|
|
669
|
-
// Extract template components using the helper from App
|
|
670
|
-
const templateComponents = extractTemplateComponents(template);
|
|
671
|
-
// Add each template component to the form using the normal drop handler
|
|
672
|
-
if (templateComponents && templateComponents.length > 0) {
|
|
673
|
-
templateComponents.forEach((component) => {
|
|
674
|
-
onDrop(component, dropTargetIndexRef.current, dropPositionRef.current);
|
|
675
|
-
if (dropTargetIndexRef.current !== null && dropPositionRef.current) {
|
|
676
|
-
dropTargetIndexRef.current = dropTargetIndexRef.current + 1;
|
|
677
|
-
}
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
else {
|
|
681
|
-
console.warn(`Template "${template.title}" contains no components`);
|
|
682
|
-
}
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
catch (error) {
|
|
686
|
-
console.error('Error processing template drop:', error);
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
// Fall back to regular component drop (data-component-json)
|
|
692
|
-
cloneElement = document.querySelector('.palette-item-clone[data-component-json]');
|
|
693
|
-
let componentData;
|
|
694
|
-
if (cloneElement) {
|
|
695
|
-
componentData = cloneElement.getAttribute('data-component-json');
|
|
696
|
-
}
|
|
697
|
-
if (!componentData)
|
|
698
|
-
return;
|
|
699
|
-
const paletteComponent = JSON.parse(componentData);
|
|
700
|
-
const newComponent = {
|
|
701
|
-
id: generateId(paletteComponent.type),
|
|
702
|
-
type: paletteComponent.type,
|
|
703
|
-
label: paletteComponent.label,
|
|
704
|
-
name: generateName(paletteComponent.type)
|
|
705
|
-
};
|
|
706
|
-
// Pass drop position info to onDrop
|
|
707
|
-
onDrop(newComponent, dropTargetIndexRef.current, dropPositionRef.current);
|
|
708
|
-
// Trigger onFieldDropped event
|
|
709
|
-
if (onFieldDropped) {
|
|
710
|
-
onFieldDropped({
|
|
711
|
-
componentName: newComponent.name,
|
|
712
|
-
componentType: newComponent.type,
|
|
713
|
-
sourceContainerId: draggedComponentInfo?.containerId || 'palette',
|
|
714
|
-
targetContainerId: 'root',
|
|
715
|
-
isReordered: false
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
// Clear drop position refs after drop
|
|
719
|
-
dropTargetIndexRef.current = null;
|
|
720
|
-
dropPositionRef.current = null;
|
|
721
|
-
};
|
|
722
|
-
const findFilteredClosest = (target, selector, blockedSelectors = []) => {
|
|
723
|
-
let el = target;
|
|
724
|
-
while (el) {
|
|
725
|
-
if (blockedSelectors.some(cls => el.classList?.contains(cls)) &&
|
|
726
|
-
!el.matches(selector)) {
|
|
727
|
-
return null;
|
|
728
|
-
}
|
|
729
|
-
if (el.matches?.(selector)) {
|
|
730
|
-
return el;
|
|
731
|
-
}
|
|
732
|
-
el = el.parentElement;
|
|
733
|
-
}
|
|
734
|
-
return null;
|
|
735
|
-
};
|
|
736
|
-
const handleNestedDrop = (parentId, cellInfo, parentPath) => {
|
|
737
|
-
if (isProcessingNestedDropRef.current) {
|
|
738
|
-
isDraggingFromPaletteRef.current = false;
|
|
739
|
-
clearDropTargetVisuals();
|
|
740
|
-
clearNestedDropTargetVisuals();
|
|
741
|
-
return; // Already processing, prevent duplicate
|
|
742
|
-
}
|
|
743
|
-
isProcessingNestedDropRef.current = true;
|
|
744
|
-
setTimeout(() => {
|
|
745
|
-
isProcessingNestedDropRef.current = false;
|
|
746
|
-
}, 50);
|
|
747
|
-
setDraggingOverPanel(null);
|
|
748
|
-
setIsDraggingOver(false);
|
|
749
|
-
// Clear palette drag flag
|
|
750
|
-
isDraggingFromPaletteRef.current = false;
|
|
751
|
-
// Clear drop target visual feedback
|
|
752
|
-
clearNestedDropTargetVisuals();
|
|
753
|
-
// Clear nested drop target visual feedback
|
|
754
|
-
['.panel-child-field-draggable', '.tab-child-field-draggable', '.table-cell-child-draggable'].forEach(selector => {
|
|
755
|
-
const fields = document.querySelectorAll(selector);
|
|
756
|
-
fields.forEach(field => {
|
|
757
|
-
field.classList.remove('drop-target-before', 'drop-target-after');
|
|
758
|
-
});
|
|
759
|
-
});
|
|
760
|
-
// Check if this is an internal move from root/different panel to this panel
|
|
761
|
-
if (draggedComponentInfo && draggedComponentInfo.containerId !== parentId) {
|
|
762
|
-
const component = findComponentById(draggedComponentInfo.id);
|
|
763
|
-
if (component) {
|
|
764
|
-
onNestedDrop(parentId, component, cellInfo, parentPath);
|
|
765
|
-
onDelete(component.id, true);
|
|
766
|
-
setDraggedComponentInfo(null);
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
// Check for template drop first (data-template-json)
|
|
771
|
-
let cloneElement = document.querySelector('.palette-item-clone[data-template-json]');
|
|
772
|
-
if (cloneElement) {
|
|
773
|
-
const templateData = cloneElement.getAttribute('data-template-json');
|
|
774
|
-
if (templateData) {
|
|
775
|
-
try {
|
|
776
|
-
const template = JSON.parse(templateData);
|
|
777
|
-
// Extract template components using the helper from App
|
|
778
|
-
const templateComponents = extractTemplateComponents(template);
|
|
779
|
-
// Add each template component to the nested container using the normal handler
|
|
780
|
-
if (templateComponents && templateComponents.length > 0) {
|
|
781
|
-
templateComponents.forEach((component) => {
|
|
782
|
-
onNestedDrop(parentId, component, cellInfo, parentPath, nestedDropTargetIndexRef.current, nestedDropPositionRef.current);
|
|
783
|
-
if (nestedDropTargetIndexRef.current !== null && nestedDropPositionRef.current) {
|
|
784
|
-
nestedDropTargetIndexRef.current = nestedDropTargetIndexRef.current + 1;
|
|
785
|
-
}
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
else {
|
|
789
|
-
console.warn(`Template "${template.title}" contains no components`);
|
|
790
|
-
}
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
catch (error) {
|
|
794
|
-
console.error('Error processing nested template drop:', error);
|
|
795
|
-
return;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
// Fall back to regular component drop (data-component-json)
|
|
800
|
-
cloneElement = document.querySelector('.palette-item-clone[data-component-json]');
|
|
801
|
-
let componentData;
|
|
802
|
-
if (cloneElement) {
|
|
803
|
-
componentData = cloneElement.getAttribute('data-component-json');
|
|
804
|
-
}
|
|
805
|
-
if (!componentData)
|
|
806
|
-
return;
|
|
807
|
-
const paletteComponent = JSON.parse(componentData);
|
|
808
|
-
const newComponent = {
|
|
809
|
-
id: generateId(paletteComponent.type),
|
|
810
|
-
type: paletteComponent.type,
|
|
811
|
-
label: paletteComponent.label,
|
|
812
|
-
name: generateName(paletteComponent.type)
|
|
813
|
-
};
|
|
814
|
-
// Call the parent handler to update nested component
|
|
815
|
-
onNestedDrop(parentId, newComponent, cellInfo, parentPath, nestedDropTargetIndexRef.current, nestedDropPositionRef.current);
|
|
816
|
-
// Trigger onFieldDropped event
|
|
817
|
-
if (onFieldDropped) {
|
|
818
|
-
onFieldDropped({
|
|
819
|
-
componentName: newComponent.name,
|
|
820
|
-
componentType: newComponent.type,
|
|
821
|
-
sourceContainerId: draggedComponentInfo?.containerId || 'palette',
|
|
822
|
-
targetContainerId: parentId,
|
|
823
|
-
isReordered: false
|
|
824
|
-
});
|
|
825
|
-
}
|
|
826
|
-
};
|
|
827
|
-
const handleNestedDragOver = (args, panelId) => {
|
|
828
|
-
// Don't highlight nested drop zones during reordering
|
|
829
|
-
setDraggingOverPanel(panelId);
|
|
830
|
-
// Check if this is a palette drag by looking for palette clone
|
|
831
|
-
const paletteClone = document.querySelector('.palette-item-clone');
|
|
832
|
-
if (paletteClone) {
|
|
833
|
-
isDraggingFromPaletteRef.current = true;
|
|
834
|
-
}
|
|
835
|
-
// DETECT DROP POSITION during palette drag - CONTINUOUSLY based on current mouse position
|
|
836
|
-
if (args && args.event && args.event.clientY !== undefined && !nestedReOrdering.current) {
|
|
837
|
-
const target = document.elementFromPoint(args.event.clientX, args.event.clientY);
|
|
838
|
-
const isTableCell = target?.classList.contains('table-cell') || null;
|
|
839
|
-
const isTabDropZone = target?.classList.contains('tab-drop-zone') || null;
|
|
840
|
-
const isPanelDropZone = target?.classList.contains('panel-drop-zone') || null;
|
|
841
|
-
let tableCell = target?.classList.contains('table-cell') ? target : findFilteredClosest(target, '.table-cell', ['panel-drop-zone', 'tab-drop-zone']);
|
|
842
|
-
let tabDropZone = target?.classList.contains('tab-drop-zone') ? target : findFilteredClosest(target, '.tab-drop-zone', ['panel-drop-zone']);
|
|
843
|
-
let panelDropZone = target?.classList.contains('panel-drop-zone') ? target : findFilteredClosest(target, '.panel-drop-zone', ['tab-drop-zone']);
|
|
844
|
-
if (isTableCell) {
|
|
845
|
-
tabDropZone = null;
|
|
846
|
-
panelDropZone = null;
|
|
847
|
-
}
|
|
848
|
-
if (isTabDropZone) {
|
|
849
|
-
tableCell = null;
|
|
850
|
-
panelDropZone = null;
|
|
851
|
-
}
|
|
852
|
-
if (isPanelDropZone) {
|
|
853
|
-
tableCell = null;
|
|
854
|
-
tabDropZone = null;
|
|
855
|
-
}
|
|
856
|
-
let containerSelector = '.form-field-draggable'; // default to root
|
|
857
|
-
if (tableCell) {
|
|
858
|
-
// Mouse is over a table cell
|
|
859
|
-
containerSelector = '.table-cell-child-draggable';
|
|
860
|
-
}
|
|
861
|
-
else if (tabDropZone) {
|
|
862
|
-
// Mouse is over a tab
|
|
863
|
-
containerSelector = '.tab-child-field-draggable';
|
|
864
|
-
}
|
|
865
|
-
else if (panelDropZone) {
|
|
866
|
-
// Mouse is over a panel/card
|
|
867
|
-
containerSelector = '.panel-child-field-draggable';
|
|
868
|
-
}
|
|
869
|
-
const result = findDropTarget(args.event.clientY, containerSelector, tableCell || tabDropZone || panelDropZone || undefined);
|
|
870
|
-
if (result) {
|
|
871
|
-
// Update refs immediately
|
|
872
|
-
nestedDropTargetIndexRef.current = result.index;
|
|
873
|
-
nestedDropPositionRef.current = result.position;
|
|
874
|
-
// Immediately apply visual classes to indicate drop position
|
|
875
|
-
updateNestedDropTargetVisuals(result, containerSelector);
|
|
876
|
-
}
|
|
877
|
-
else {
|
|
878
|
-
// Clear drop target if not over any field
|
|
879
|
-
nestedDropTargetIndexRef.current = null;
|
|
880
|
-
nestedDropPositionRef.current = null;
|
|
881
|
-
clearNestedDropTargetVisuals();
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
// Trigger onFieldDraggingOver event
|
|
885
|
-
if (onFieldDraggingOver) {
|
|
886
|
-
onFieldDraggingOver({
|
|
887
|
-
isNested: true
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
};
|
|
891
|
-
const updateNestedDropTargetVisuals = (target, containerSelector, container) => {
|
|
892
|
-
const fields = container ? container.querySelectorAll(containerSelector) : document.querySelectorAll(containerSelector);
|
|
893
|
-
fields.forEach((field, idx) => {
|
|
894
|
-
field.classList.remove('drop-target-before', 'drop-target-after');
|
|
895
|
-
if (idx === target.index) {
|
|
896
|
-
field.classList.add(`drop-target-${target.position}`);
|
|
897
|
-
}
|
|
898
|
-
});
|
|
899
|
-
};
|
|
900
|
-
// Helper to clear nested drop target visual feedback
|
|
901
|
-
const clearNestedDropTargetVisuals = () => {
|
|
902
|
-
['.panel-child-field-draggable', '.tab-child-field-draggable', '.table-cell-child-draggable'].forEach(selector => {
|
|
903
|
-
const fields = document.querySelectorAll(selector);
|
|
904
|
-
fields.forEach(field => {
|
|
905
|
-
field.classList.remove('drop-target-before', 'drop-target-after');
|
|
906
|
-
});
|
|
907
|
-
});
|
|
908
|
-
};
|
|
909
|
-
useEffect(() => {
|
|
910
|
-
// Extract pointer coordinates from either a MouseEvent or a TouchEvent.
|
|
911
|
-
// Touch events store their coordinates in `touches[0]`, not on the event itself.
|
|
912
|
-
const getPoint = (e) => {
|
|
913
|
-
if ('touches' in e) {
|
|
914
|
-
// touchend has changedTouches; touchmove/touchstart have touches
|
|
915
|
-
const touch = e.touches[0] || e.changedTouches?.[0];
|
|
916
|
-
if (!touch) {
|
|
917
|
-
return null;
|
|
918
|
-
}
|
|
919
|
-
return { clientX: touch.clientX, clientY: touch.clientY };
|
|
920
|
-
}
|
|
921
|
-
return { clientX: e.clientX, clientY: e.clientY };
|
|
922
|
-
};
|
|
923
|
-
const handleGlobalPointerMove = (e) => {
|
|
924
|
-
if (!isDraggingFromPaletteRef.current) {
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
const point = getPoint(e);
|
|
928
|
-
if (!point) {
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
// DYNAMICALLY DETECT which container the pointer is over
|
|
932
|
-
const target = document.elementFromPoint(point.clientX, point.clientY);
|
|
933
|
-
// Check for nested containers first (most specific to least specific)
|
|
934
|
-
const isTableCell = target?.classList.contains('table-cell') || null;
|
|
935
|
-
const isTabDropZone = target?.classList.contains('tab-drop-zone') || null;
|
|
936
|
-
const isPanelDropZone = target?.classList.contains('panel-drop-zone') || null;
|
|
937
|
-
let tableCell = target?.classList.contains('table-cell') ? target : findFilteredClosest(target, '.table-cell', ['panel-drop-zone', 'tab-drop-zone']);
|
|
938
|
-
let tabDropZone = target?.classList.contains('tab-drop-zone') ? target : findFilteredClosest(target, '.tab-drop-zone', ['panel-drop-zone']);
|
|
939
|
-
let panelDropZone = target?.classList.contains('panel-drop-zone') ? target : findFilteredClosest(target, '.panel-drop-zone', ['tab-drop-zone']);
|
|
940
|
-
if (isTableCell) {
|
|
941
|
-
tabDropZone = null;
|
|
942
|
-
panelDropZone = null;
|
|
943
|
-
}
|
|
944
|
-
if (isTabDropZone) {
|
|
945
|
-
tableCell = null;
|
|
946
|
-
panelDropZone = null;
|
|
947
|
-
}
|
|
948
|
-
if (isPanelDropZone) {
|
|
949
|
-
tableCell = null;
|
|
950
|
-
tabDropZone = null;
|
|
951
|
-
}
|
|
952
|
-
let containerSelector = '.form-field-draggable'; // default to root
|
|
953
|
-
let isNested = false;
|
|
954
|
-
if (tableCell) {
|
|
955
|
-
// Mouse is over a table cell
|
|
956
|
-
containerSelector = '.table-cell-child-draggable';
|
|
957
|
-
isNested = true;
|
|
958
|
-
}
|
|
959
|
-
else if (tabDropZone) {
|
|
960
|
-
// Mouse is over a tab
|
|
961
|
-
containerSelector = '.tab-child-field-draggable';
|
|
962
|
-
isNested = true;
|
|
963
|
-
}
|
|
964
|
-
else if (panelDropZone) {
|
|
965
|
-
// Mouse is over a panel/card
|
|
966
|
-
containerSelector = '.panel-child-field-draggable';
|
|
967
|
-
isNested = true;
|
|
968
|
-
}
|
|
969
|
-
// Find drop target with the correct selector
|
|
970
|
-
const result = findDropTarget(point.clientY, containerSelector, tableCell || tabDropZone || panelDropZone || undefined);
|
|
971
|
-
if (result) {
|
|
972
|
-
if (isNested) {
|
|
973
|
-
// Update nested refs
|
|
974
|
-
clearNestedDropTargetVisuals();
|
|
975
|
-
nestedDropTargetIndexRef.current = result.index;
|
|
976
|
-
nestedDropPositionRef.current = result.position;
|
|
977
|
-
updateNestedDropTargetVisuals(result, containerSelector, tableCell || tabDropZone || panelDropZone || undefined);
|
|
978
|
-
// Clear root refs
|
|
979
|
-
dropTargetIndexRef.current = null;
|
|
980
|
-
dropPositionRef.current = null;
|
|
981
|
-
clearDropTargetVisuals();
|
|
982
|
-
}
|
|
983
|
-
else {
|
|
984
|
-
// Update root refs
|
|
985
|
-
clearDropTargetVisuals();
|
|
986
|
-
dropTargetIndexRef.current = result.index;
|
|
987
|
-
dropPositionRef.current = result.position;
|
|
988
|
-
updateDropTargetVisuals(result);
|
|
989
|
-
// Clear nested refs
|
|
990
|
-
nestedDropTargetIndexRef.current = null;
|
|
991
|
-
nestedDropPositionRef.current = null;
|
|
992
|
-
clearNestedDropTargetVisuals();
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
else {
|
|
996
|
-
// Clear all if not over any field
|
|
997
|
-
dropTargetIndexRef.current = null;
|
|
998
|
-
dropPositionRef.current = null;
|
|
999
|
-
nestedDropTargetIndexRef.current = null;
|
|
1000
|
-
nestedDropPositionRef.current = null;
|
|
1001
|
-
clearDropTargetVisuals();
|
|
1002
|
-
clearNestedDropTargetVisuals();
|
|
1003
|
-
}
|
|
1004
|
-
};
|
|
1005
|
-
document.addEventListener('mousemove', handleGlobalPointerMove);
|
|
1006
|
-
document.addEventListener('touchmove', handleGlobalPointerMove, { passive: true });
|
|
1007
|
-
return () => {
|
|
1008
|
-
document.removeEventListener('mousemove', handleGlobalPointerMove);
|
|
1009
|
-
document.removeEventListener('touchmove', handleGlobalPointerMove);
|
|
1010
|
-
};
|
|
1011
|
-
}, [isReordering, isNestedReordering, reOrdering.current, findDropTarget, updateNestedDropTargetVisuals, clearNestedDropTargetVisuals]);
|
|
1012
|
-
const handleNestedDragLeave = () => {
|
|
1013
|
-
setDraggingOverPanel(null);
|
|
1014
|
-
isDraggingFromPaletteRef.current = false;
|
|
1015
|
-
clearDropTargetVisuals();
|
|
1016
|
-
clearNestedDropTargetVisuals();
|
|
1017
|
-
// Clear drop target visual feedback
|
|
1018
|
-
nestedDropTargetIndexRef.current = null;
|
|
1019
|
-
nestedDropPositionRef.current = null;
|
|
1020
|
-
};
|
|
1021
|
-
// Nested reorder handlers
|
|
1022
|
-
const isValidNestedTarget = (element, containerClass) => {
|
|
1023
|
-
return element && element.classList.contains(containerClass);
|
|
1024
|
-
};
|
|
1025
|
-
const nestedHelper = (args, containerClass) => {
|
|
1026
|
-
const nestedField = (args.sender?.target).closest(`.${containerClass}`);
|
|
1027
|
-
if (!nestedField || !isValidNestedTarget(nestedField, containerClass)) {
|
|
1028
|
-
return null;
|
|
1029
|
-
}
|
|
1030
|
-
const index = parseInt(nestedField.getAttribute('data-nested-index') || '0', 10);
|
|
1031
|
-
nestedDraggedIndex.current = index;
|
|
1032
|
-
return nestedCloneHelperRef.current;
|
|
1033
|
-
};
|
|
1034
|
-
const nestedDragStart = (e) => {
|
|
1035
|
-
if (nestedCloneHelperRef.current) {
|
|
1036
|
-
nestedCloneHelperRef.current.style.display = 'flex';
|
|
1037
|
-
}
|
|
1038
|
-
// CAPTURE INITIAL POSITION from mouse event
|
|
1039
|
-
if (e && (e.event.clientY !== undefined || e.event.touches[0].clientY !== undefined)) {
|
|
1040
|
-
nestedInitialDragYRef.current = e.event.clientY || e.event.touches[0].clientY;
|
|
1041
|
-
nestedCurrentDragYRef.current = e.event.clientY || e.event.touches[0].clientY;
|
|
1042
|
-
}
|
|
1043
|
-
// Capture what's being dragged from nested container
|
|
1044
|
-
if (nestedDraggedIndex.current !== null && nestedComponentsRef.current[nestedDraggedIndex.current]) {
|
|
1045
|
-
const component = nestedComponentsRef.current[nestedDraggedIndex.current];
|
|
1046
|
-
setDraggedComponentInfo({
|
|
1047
|
-
id: component.id,
|
|
1048
|
-
containerId: nestedContainerIdRef.current || ''
|
|
1049
|
-
});
|
|
1050
|
-
// Add class to the actual DOM element being dragged
|
|
1051
|
-
const draggedElement = document.querySelector(`[data-component-id="${component.id}"]`);
|
|
1052
|
-
if (draggedElement) {
|
|
1053
|
-
draggedElement.classList.add('is-being-dragged');
|
|
1054
|
-
}
|
|
1055
|
-
// Trigger onFieldDragStarted event
|
|
1056
|
-
if (onFieldDragStarted) {
|
|
1057
|
-
onFieldDragStarted({
|
|
1058
|
-
componentName: component.name,
|
|
1059
|
-
componentType: component.type,
|
|
1060
|
-
sourceContainerId: nestedContainerIdRef.current || '',
|
|
1061
|
-
isNested: true
|
|
1062
|
-
});
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
};
|
|
1066
|
-
const nestedDrag = (e) => {
|
|
1067
|
-
if (nestedComponentsRef.current.length > 1) {
|
|
1068
|
-
setIsNestedReordering(true);
|
|
1069
|
-
nestedReOrdering.current = true;
|
|
1070
|
-
}
|
|
1071
|
-
if (nestedCloneHelperRef.current) {
|
|
1072
|
-
nestedCloneHelperRef.current.style.opacity = '0.97';
|
|
1073
|
-
// DETERMINE DIRECTION and add appropriate class
|
|
1074
|
-
if (e && (e.event.clientY !== undefined || e.event.touches[0].clientY !== undefined) && nestedInitialDragYRef.current !== null) {
|
|
1075
|
-
const currentY = e.event.clientY || e.event.touches[0].clientY;
|
|
1076
|
-
const initialY = nestedInitialDragYRef.current;
|
|
1077
|
-
if (e.event?.touches?.length > 0) {
|
|
1078
|
-
const target = document.elementFromPoint(e.event.touches[0].clientX, e.event.touches[0].clientY);
|
|
1079
|
-
const wrapper = target?.closest('.form-field-wrapper');
|
|
1080
|
-
document.querySelectorAll('.form-field-wrapper.touch-hovered').forEach(el => {
|
|
1081
|
-
if (el !== wrapper)
|
|
1082
|
-
el.classList.remove('touch-hovered');
|
|
1083
|
-
});
|
|
1084
|
-
if (wrapper && !wrapper.classList.contains('touch-hovered')) {
|
|
1085
|
-
wrapper.classList.add('touch-hovered');
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
// ALSO apply direction class to form-preview-area for styling hover targets
|
|
1089
|
-
const formPreviewArea = document.querySelector('.form-preview-area');
|
|
1090
|
-
if (formPreviewArea) {
|
|
1091
|
-
formPreviewArea.classList.remove('drag-moving-up', 'drag-moving-down');
|
|
1092
|
-
if (currentY < initialY) {
|
|
1093
|
-
formPreviewArea.classList.add('drag-moving-up');
|
|
1094
|
-
}
|
|
1095
|
-
else if (currentY > initialY) {
|
|
1096
|
-
formPreviewArea.classList.add('drag-moving-down');
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
// UPDATE current position ref for tracking
|
|
1100
|
-
nestedCurrentDragYRef.current = currentY;
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
};
|
|
1104
|
-
const nestedDragStop = (args, containerClass) => {
|
|
1105
|
-
setIsNestedReordering(false);
|
|
1106
|
-
nestedReOrdering.current = false;
|
|
1107
|
-
setIsDraggingOver(false);
|
|
1108
|
-
setDraggingOverPanel(null);
|
|
1109
|
-
setActiveNestedContainer(null);
|
|
1110
|
-
// RESET nested drag position tracking on stop
|
|
1111
|
-
nestedInitialDragYRef.current = null;
|
|
1112
|
-
nestedCurrentDragYRef.current = null;
|
|
1113
|
-
// Remove direction classes from nested clone
|
|
1114
|
-
if (nestedCloneHelperRef.current) {
|
|
1115
|
-
nestedCloneHelperRef.current.classList.remove('moving-up', 'moving-down');
|
|
1116
|
-
nestedCloneHelperRef.current.style.display = 'none';
|
|
1117
|
-
}
|
|
1118
|
-
if (nestedDraggedIndex.current === null)
|
|
1119
|
-
return;
|
|
1120
|
-
const component = nestedComponentsRef.current[nestedDraggedIndex.current];
|
|
1121
|
-
const sourceContainerId = nestedContainerIdRef.current;
|
|
1122
|
-
const target = args.target;
|
|
1123
|
-
// Remove class from the dragged element
|
|
1124
|
-
if (component) {
|
|
1125
|
-
const draggedElement = document.querySelector(`[data-component-id="${component.id}"]`);
|
|
1126
|
-
if (draggedElement) {
|
|
1127
|
-
draggedElement.classList.remove('is-being-dragged');
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
// Check if dropped on root (cross-container move to root)
|
|
1131
|
-
const formPreviewArea = target?.closest('.form-preview-area');
|
|
1132
|
-
const isOverAnyNested = target?.closest('.panel-drop-zone, .tab-drop-zone, .table-cell');
|
|
1133
|
-
const closestDropZone = target?.closest('.panel-drop-zone, .tab-drop-zone, .table-cell');
|
|
1134
|
-
const closestDropZoneId = closestDropZone?.getAttribute('data-panel-id') ||
|
|
1135
|
-
closestDropZone?.getAttribute('data-container-id') ||
|
|
1136
|
-
null;
|
|
1137
|
-
// Extract target cell/tab info from closest drop zone
|
|
1138
|
-
const targetCellRow = closestDropZone?.classList.contains('table-cell')
|
|
1139
|
-
? parseInt(closestDropZone.getAttribute('data-cell-row') || '0', 10)
|
|
1140
|
-
: undefined;
|
|
1141
|
-
const targetCellCol = closestDropZone?.classList.contains('table-cell')
|
|
1142
|
-
? parseInt(closestDropZone.getAttribute('data-cell-col') || '0', 10)
|
|
1143
|
-
: undefined;
|
|
1144
|
-
const targetTabIndex = closestDropZone?.classList.contains('tab-drop-zone')
|
|
1145
|
-
? parseInt(closestDropZone.getAttribute('data-tab-index') || '0', 10)
|
|
1146
|
-
: undefined;
|
|
1147
|
-
// Determine if dragging within same parent container
|
|
1148
|
-
const isSameParent = nestedCellInfoRef.current?.row !== undefined && nestedCellInfoRef.current?.col !== undefined
|
|
1149
|
-
? // Source is from a table cell - check if target is the same cell
|
|
1150
|
-
(sourceContainerId === closestDropZoneId &&
|
|
1151
|
-
nestedCellInfoRef.current?.row === targetCellRow &&
|
|
1152
|
-
nestedCellInfoRef.current?.col === targetCellCol)
|
|
1153
|
-
: nestedCellInfoRef.current?.tabIndex !== undefined
|
|
1154
|
-
? // Source is from a tab - check if target is the same tab
|
|
1155
|
-
(sourceContainerId === closestDropZoneId &&
|
|
1156
|
-
nestedCellInfoRef.current?.tabIndex === targetTabIndex)
|
|
1157
|
-
: // Source is from a panel - check if target is the same panel
|
|
1158
|
-
sourceContainerId === closestDropZoneId;
|
|
1159
|
-
if (formPreviewArea && !isOverAnyNested && component) {
|
|
1160
|
-
// DELETE FIRST, then ADD
|
|
1161
|
-
onDelete(component.id, true);
|
|
1162
|
-
setTimeout(() => {
|
|
1163
|
-
onDrop(component, dropTargetIndexRef.current, dropPositionRef.current);
|
|
1164
|
-
}, 10);
|
|
1165
|
-
isDraggingFromPaletteRef.current = false;
|
|
1166
|
-
clearDropTargetVisuals();
|
|
1167
|
-
nestedDraggedIndex.current = null;
|
|
1168
|
-
setDraggedComponentInfo(null);
|
|
1169
|
-
if (onFieldDropped) {
|
|
1170
|
-
onFieldDropped({
|
|
1171
|
-
componentName: component.name,
|
|
1172
|
-
componentType: component.type,
|
|
1173
|
-
sourceContainerId: sourceContainerId || 'root',
|
|
1174
|
-
targetContainerId: 'root',
|
|
1175
|
-
isReordered: false
|
|
1176
|
-
});
|
|
1177
|
-
}
|
|
1178
|
-
return;
|
|
1179
|
-
}
|
|
1180
|
-
// Check if dropped on a different panel (cross-container move)
|
|
1181
|
-
const targetPanelDropZone = target?.closest('.panel-drop-zone');
|
|
1182
|
-
if (targetPanelDropZone && !isSameParent) {
|
|
1183
|
-
const targetPanelId = targetPanelDropZone.getAttribute('data-panel-id');
|
|
1184
|
-
if (targetPanelId && component) {
|
|
1185
|
-
// Prevent dropping into itself or its own descendant
|
|
1186
|
-
if (targetPanelId === component.id || isIdInSubtree(component, targetPanelId)) {
|
|
1187
|
-
nestedDraggedIndex.current = null;
|
|
1188
|
-
setDraggedComponentInfo(null);
|
|
1189
|
-
isDraggingFromPaletteRef.current = false;
|
|
1190
|
-
clearNestedDropTargetVisuals();
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1193
|
-
if (targetPanelId !== sourceContainerId && (sourceContainerId?.includes('panel') || sourceContainerId?.includes('card') || closestDropZoneId?.includes('panel') || closestDropZoneId?.includes('card'))) {
|
|
1194
|
-
// Move to different panel or card
|
|
1195
|
-
onDelete(component.id, true);
|
|
1196
|
-
setTimeout(() => {
|
|
1197
|
-
onNestedDrop(targetPanelId, component, undefined, targetPanelId, nestedDropTargetIndexRef.current, nestedDropPositionRef.current);
|
|
1198
|
-
}, 0);
|
|
1199
|
-
isDraggingFromPaletteRef.current = false;
|
|
1200
|
-
clearNestedDropTargetVisuals();
|
|
1201
|
-
nestedDraggedIndex.current = null;
|
|
1202
|
-
setDraggedComponentInfo(null);
|
|
1203
|
-
if (onFieldDropped) {
|
|
1204
|
-
onFieldDropped({
|
|
1205
|
-
componentName: component.name,
|
|
1206
|
-
componentType: component.type,
|
|
1207
|
-
sourceContainerId: sourceContainerId || 'root',
|
|
1208
|
-
targetContainerId: targetPanelId,
|
|
1209
|
-
isReordered: false
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
1212
|
-
return;
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
// Check if dropped on a tab drop zone (cross-container move)
|
|
1217
|
-
const targetTabDropZone = target?.closest('.tab-drop-zone');
|
|
1218
|
-
if (targetTabDropZone && !isSameParent) {
|
|
1219
|
-
const targetTabContainerId = targetTabDropZone.getAttribute('data-container-id');
|
|
1220
|
-
const targetTabIndex = parseInt(targetTabDropZone.getAttribute('data-tab-index') || '0', 10);
|
|
1221
|
-
// Check if different container or different tab
|
|
1222
|
-
const isSameTabContainer = targetTabContainerId === sourceContainerId &&
|
|
1223
|
-
nestedCellInfoRef.current?.tabIndex === targetTabIndex;
|
|
1224
|
-
if (targetTabContainerId && component) {
|
|
1225
|
-
// Prevent dropping into itself or its own descendant
|
|
1226
|
-
if (targetTabContainerId === component.id || isIdInSubtree(component, targetTabContainerId)) {
|
|
1227
|
-
nestedDraggedIndex.current = null;
|
|
1228
|
-
setDraggedComponentInfo(null);
|
|
1229
|
-
isDraggingFromPaletteRef.current = false;
|
|
1230
|
-
clearNestedDropTargetVisuals();
|
|
1231
|
-
return;
|
|
1232
|
-
}
|
|
1233
|
-
if (!isSameTabContainer && (sourceContainerId?.includes('tabs') || sourceContainerId?.includes('card') || closestDropZoneId?.includes('tabs') || closestDropZoneId?.includes('card'))) {
|
|
1234
|
-
// Move to different tab or card
|
|
1235
|
-
onDelete(component.id, true);
|
|
1236
|
-
setTimeout(() => {
|
|
1237
|
-
onNestedDrop(targetTabContainerId, component, { row: 0, col: 0, tabIndex: targetTabIndex }, targetTabContainerId, nestedDropTargetIndexRef.current, nestedDropPositionRef.current);
|
|
1238
|
-
}, 0);
|
|
1239
|
-
isDraggingFromPaletteRef.current = false;
|
|
1240
|
-
clearNestedDropTargetVisuals();
|
|
1241
|
-
nestedDraggedIndex.current = null;
|
|
1242
|
-
setDraggedComponentInfo(null);
|
|
1243
|
-
if (onFieldDropped) {
|
|
1244
|
-
onFieldDropped({
|
|
1245
|
-
componentName: component.name,
|
|
1246
|
-
componentType: component.type,
|
|
1247
|
-
sourceContainerId: sourceContainerId || 'root',
|
|
1248
|
-
targetContainerId: targetTabContainerId,
|
|
1249
|
-
isReordered: false
|
|
1250
|
-
});
|
|
1251
|
-
}
|
|
1252
|
-
return;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
// Check if dropped on a table cell (cross-container move)
|
|
1257
|
-
const targetTableCell = target?.closest('.table-cell');
|
|
1258
|
-
if (targetTableCell && !isSameParent) {
|
|
1259
|
-
const targetCellContainerId = targetTableCell.getAttribute('data-container-id');
|
|
1260
|
-
const targetCellRow = parseInt(targetTableCell.getAttribute('data-cell-row') || '0', 10);
|
|
1261
|
-
const targetCellCol = parseInt(targetTableCell.getAttribute('data-cell-col') || '0', 10);
|
|
1262
|
-
// Check if different container or different cell
|
|
1263
|
-
const isSameCell = targetCellContainerId === sourceContainerId &&
|
|
1264
|
-
nestedCellInfoRef.current?.row === targetCellRow &&
|
|
1265
|
-
nestedCellInfoRef.current?.col === targetCellCol;
|
|
1266
|
-
if (targetCellContainerId && component) {
|
|
1267
|
-
// Prevent dropping into itself or its own descendant
|
|
1268
|
-
if (targetCellContainerId === component.id || isIdInSubtree(component, targetCellContainerId)) {
|
|
1269
|
-
nestedDraggedIndex.current = null;
|
|
1270
|
-
setDraggedComponentInfo(null);
|
|
1271
|
-
isDraggingFromPaletteRef.current = false;
|
|
1272
|
-
clearNestedDropTargetVisuals();
|
|
1273
|
-
return;
|
|
1274
|
-
}
|
|
1275
|
-
if (!isSameCell) {
|
|
1276
|
-
// Move to different cell
|
|
1277
|
-
onDelete(component.id, true);
|
|
1278
|
-
setTimeout(() => {
|
|
1279
|
-
onNestedDrop(targetCellContainerId, component, { row: targetCellRow, col: targetCellCol }, targetCellContainerId, nestedDropTargetIndexRef.current, nestedDropPositionRef.current);
|
|
1280
|
-
}, 0);
|
|
1281
|
-
isDraggingFromPaletteRef.current = false;
|
|
1282
|
-
clearNestedDropTargetVisuals();
|
|
1283
|
-
nestedDraggedIndex.current = null;
|
|
1284
|
-
setDraggedComponentInfo(null);
|
|
1285
|
-
if (onFieldDropped) {
|
|
1286
|
-
onFieldDropped({
|
|
1287
|
-
componentName: component.name,
|
|
1288
|
-
componentType: component.type,
|
|
1289
|
-
sourceContainerId: sourceContainerId || 'root',
|
|
1290
|
-
targetContainerId: targetCellContainerId,
|
|
1291
|
-
isReordered: false
|
|
1292
|
-
});
|
|
1293
|
-
}
|
|
1294
|
-
return;
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
// Same container reorder (existing logic)
|
|
1299
|
-
const targetElement = args.target?.closest(`.${containerClass}`);
|
|
1300
|
-
if (targetElement) {
|
|
1301
|
-
const targetIndex = parseInt(targetElement.getAttribute('data-nested-index') || '0', 10);
|
|
1302
|
-
if (targetIndex !== -1 && targetIndex !== nestedDraggedIndex.current) {
|
|
1303
|
-
const newChildren = [...nestedComponentsRef.current];
|
|
1304
|
-
const draggedChild = newChildren[nestedDraggedIndex.current];
|
|
1305
|
-
newChildren.splice(nestedDraggedIndex.current, 1);
|
|
1306
|
-
newChildren.splice(targetIndex, 0, draggedChild);
|
|
1307
|
-
// Call parent to update
|
|
1308
|
-
if (nestedContainerIdRef.current && nestedContainerPathRef.current) {
|
|
1309
|
-
onNestedReorder(nestedContainerIdRef.current, nestedContainerPathRef.current, newChildren, nestedCellInfoRef.current);
|
|
1310
|
-
isDraggingFromPaletteRef.current = false;
|
|
1311
|
-
clearDropTargetVisuals();
|
|
1312
|
-
clearNestedDropTargetVisuals();
|
|
1313
|
-
}
|
|
1314
|
-
// Trigger onFieldDropped event for reordering
|
|
1315
|
-
if (onFieldDropped) {
|
|
1316
|
-
onFieldDropped({
|
|
1317
|
-
componentName: draggedChild.name,
|
|
1318
|
-
componentType: draggedChild.type,
|
|
1319
|
-
sourceContainerId: sourceContainerId || 'root',
|
|
1320
|
-
targetContainerId: nestedContainerIdRef.current || 'root',
|
|
1321
|
-
isReordered: true
|
|
1322
|
-
});
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
nestedDraggedIndex.current = null;
|
|
1327
|
-
isDraggingFromPaletteRef.current = false;
|
|
1328
|
-
clearDropTargetVisuals();
|
|
1329
|
-
clearNestedDropTargetVisuals();
|
|
1330
|
-
setDraggedComponentInfo(null); // Clear tracking
|
|
1331
|
-
};
|
|
1332
|
-
const handleListBoxItemSelect = useCallback((component) => {
|
|
1333
|
-
// Create a new instance of the selected component with proper type casting
|
|
1334
|
-
const newComponent = {
|
|
1335
|
-
id: generateId(component.type),
|
|
1336
|
-
type: component.type,
|
|
1337
|
-
label: component.label || '',
|
|
1338
|
-
name: generateName(component.type),
|
|
1339
|
-
// Add default properties as needed
|
|
1340
|
-
};
|
|
1341
|
-
// Call onDrop with the new component (append to end)
|
|
1342
|
-
if (onDrop) {
|
|
1343
|
-
onDrop(newComponent);
|
|
1344
|
-
}
|
|
1345
|
-
setIsListBoxPaneOpen(false);
|
|
1346
|
-
}, [onDrop, formSchema.components.length]);
|
|
1347
|
-
const getNestedChildren = (parentId, _parentPath, cellInfo) => {
|
|
1348
|
-
const parentComponent = findComponentById(parentId);
|
|
1349
|
-
if (!parentComponent)
|
|
1350
|
-
return [];
|
|
1351
|
-
if (cellInfo?.tabIndex !== undefined) {
|
|
1352
|
-
return parentComponent.tabItems?.[cellInfo.tabIndex]?.content || [];
|
|
1353
|
-
}
|
|
1354
|
-
if (cellInfo?.row !== undefined && cellInfo?.col !== undefined) {
|
|
1355
|
-
return parentComponent.tableCells?.[cellInfo.row]?.[cellInfo.col] || [];
|
|
1356
|
-
}
|
|
1357
|
-
return parentComponent.children || [];
|
|
1358
|
-
};
|
|
1359
|
-
const handleMoveUp = useCallback((fieldId) => {
|
|
1360
|
-
const index = currentComponentsRef.current.findIndex(c => c.id === fieldId);
|
|
1361
|
-
if (index > 0) {
|
|
1362
|
-
const newComponents = [...currentComponentsRef.current];
|
|
1363
|
-
[newComponents[index - 1], newComponents[index]] = [newComponents[index], newComponents[index - 1]];
|
|
1364
|
-
onReorder(newComponents);
|
|
1365
|
-
}
|
|
1366
|
-
}, [onReorder]);
|
|
1367
|
-
const handleMoveDown = useCallback((fieldId) => {
|
|
1368
|
-
const index = currentComponentsRef.current.findIndex(c => c.id === fieldId);
|
|
1369
|
-
if (index < currentComponentsRef.current.length - 1) {
|
|
1370
|
-
const newComponents = [...currentComponentsRef.current];
|
|
1371
|
-
[newComponents[index], newComponents[index + 1]] = [newComponents[index + 1], newComponents[index]];
|
|
1372
|
-
onReorder(newComponents);
|
|
1373
|
-
}
|
|
1374
|
-
}, [onReorder]);
|
|
1375
|
-
const handleNestedMoveUp = useCallback((fieldId, parentId, parentPath, cellInfo) => {
|
|
1376
|
-
const children = getNestedChildren(parentId, parentPath, cellInfo);
|
|
1377
|
-
const index = children.findIndex(c => c.id === fieldId);
|
|
1378
|
-
if (index > 0) {
|
|
1379
|
-
const newChildren = [...children];
|
|
1380
|
-
[newChildren[index - 1], newChildren[index]] = [newChildren[index], newChildren[index - 1]];
|
|
1381
|
-
onNestedReorder(parentId, parentPath, newChildren, cellInfo);
|
|
1382
|
-
}
|
|
1383
|
-
}, [onNestedReorder]);
|
|
1384
|
-
const handleNestedMoveDown = useCallback((fieldId, parentId, parentPath, cellInfo) => {
|
|
1385
|
-
const children = getNestedChildren(parentId, parentPath, cellInfo);
|
|
1386
|
-
const index = children.findIndex(c => c.id === fieldId);
|
|
1387
|
-
if (index < children.length - 1) {
|
|
1388
|
-
const newChildren = [...children];
|
|
1389
|
-
[newChildren[index], newChildren[index + 1]] = [newChildren[index + 1], newChildren[index]];
|
|
1390
|
-
onNestedReorder(parentId, parentPath, newChildren, cellInfo);
|
|
1391
|
-
}
|
|
1392
|
-
}, [onNestedReorder]);
|
|
1393
|
-
const renderNestedChildren = (component, children, parentPath, canReorderPanel) => {
|
|
1394
|
-
return (_jsx(Droppable, { scope: 'new-field', onOver: (args) => handleNestedDragOver(args, component.id), onOut: handleNestedDragLeave, onDrop: () => handleNestedDrop(component.id, undefined, parentPath), children: _jsx("div", { className: `panel-drop-zone ${draggingOverPanel === component.id ? 'dragging-over' : ''} ${children.length === 0 ? 'empty' : ''} ${component.cssClass}`, "data-panel-id": component.id, "aria-label": 'Drop Zone', children: children.length === 0 ? (_jsx(Droppable, { scope: 'new-field', onOver: (args) => handleNestedDragOver(args, component.id), onOut: handleNestedDragLeave, onDrop: () => handleNestedDrop(component.id, undefined, parentPath), children: _jsx("p", { className: "drop-zone-text", children: l10n.getConstant('emptyFieldSetState') }) }, `panel-${isNestedReordering}`)) : canReorderPanel ? (_jsx(Draggable, { scope: 'internal', clone: true, enableTailMode: true, isPreventSelect: true, helper: (args) => {
|
|
1395
|
-
nestedComponentsRef.current = children;
|
|
1396
|
-
nestedContainerIdRef.current = component.id;
|
|
1397
|
-
nestedContainerPathRef.current = parentPath;
|
|
1398
|
-
nestedCellInfoRef.current = undefined;
|
|
1399
|
-
setActiveNestedContainer(component.id);
|
|
1400
|
-
return nestedHelper(args, 'panel-child-field-draggable');
|
|
1401
|
-
}, onDragStart: nestedDragStart, onDragStop: (args) => nestedDragStop(args, 'panel-child-field-draggable'), onDrag: nestedDrag, children: _jsx("div", { style: { width: '100%', margin: '0px', padding: '0px' }, children: _jsx(Droppable, { scope: 'new-field', onOver: (args) => handleNestedDragOver(args, component.id), onOut: handleNestedDragLeave, onDrop: () => handleNestedDrop(component.id, undefined, parentPath), children: _jsx("div", { className: `panel-children ${isNestedReordering && activeNestedContainer === component.id ? 'is-nested-reordering' : ''}`, children: children.map((child, index) => {
|
|
1402
|
-
const isLayoutComponent = ['panel', 'table', 'tabs', 'card', 'staticHtml'].includes(child.type);
|
|
1403
|
-
const labelPos = child.labelPosition || 'top';
|
|
1404
|
-
const labelWidth = child.labelWidth + '%' || 'auto';
|
|
1405
|
-
const labelStyle = {
|
|
1406
|
-
width: labelWidth,
|
|
1407
|
-
...(labelPos.toLowerCase() === 'right' ? { marginLeft: labelWidth, width: 'unset' } : {})
|
|
1408
|
-
};
|
|
1409
|
-
return (_jsx(FormFieldWrapper, { mode: mode, isActive: activeFieldId === child.id, component: child, onEdit: onEdit, onCopy: onCopy, onPaste: onPaste, onDelete: onDelete, onSwitchWidget: onSwitchWidget, isNested: true, index: index, totalCount: children.length, hasClipboardData: hasClipboardData, nestingContext: {
|
|
1410
|
-
parentId: component.id,
|
|
1411
|
-
parentPath: parentPath,
|
|
1412
|
-
cellInfo: undefined
|
|
1413
|
-
}, onNestedMoveUp: handleNestedMoveUp, onNestedMoveDown: handleNestedMoveDown, children: _jsx(FormField, { name: child.id, children: _jsxs("div", { className: "panel-child-field panel-child-field-draggable", "data-nested-index": index, children: [_jsxs("div", { className: `field-content label-${labelPos.toLowerCase()}`, children: [!['checkbox', 'button', 'table', 'tabs', 'panel', 'message', 'card', 'splitButton', 'staticHtml'].includes(child.type) && child.label && !child.hideLabel && (_jsxs("label", { className: "field-label", htmlFor: child.id, style: {
|
|
1414
|
-
...labelStyle,
|
|
1415
|
-
display: 'flex',
|
|
1416
|
-
alignItems: 'center',
|
|
1417
|
-
gap: '6px',
|
|
1418
|
-
}, children: [_jsxs("span", { children: [child.label, child?.required && _jsx("span", { className: "required", children: "*" })] }), child?.tooltip && (_jsx(TooltipComponent, { content: child.tooltip, children: _jsx("span", { className: "e-msg-icon", style: {
|
|
1419
|
-
cursor: 'pointer',
|
|
1420
|
-
display: 'flex',
|
|
1421
|
-
alignItems: 'center',
|
|
1422
|
-
justifyContent: 'center',
|
|
1423
|
-
flexShrink: 0,
|
|
1424
|
-
} }) }))] })), _jsxs("div", { className: "field-input", inert: !['panel', 'table', 'tabs', 'card', 'staticHtml'].includes(child.type) ? true : undefined, children: [isLayoutComponent ? renderFormComponent({ ...child, parentPath: parentPath }) : renderFormComponent(child), formState?.errors[child.id] && (_jsx("div", { className: "e-error", children: formState?.errors[child.id] }))] })] }), child.description && _jsx("div", { className: 'field-description', children: child.description })] }, `field-${child.id}`) }, `field-${child.id}`) }, child.id));
|
|
1425
|
-
}) }) }, `panel-${isNestedReordering}`) }) }, `draggable-panel-${component.id}-${children.map(c => c.id).join('-')}`)) : (_jsx("div", { className: "panel-children", children: children.map((child, index) => {
|
|
1426
|
-
const isLayoutComponent = ['panel', 'table', 'tabs'].includes(child.type);
|
|
1427
|
-
const labelPos = child.labelPosition || 'top';
|
|
1428
|
-
const labelWidth = child.labelWidth + '%' || 'auto';
|
|
1429
|
-
const labelStyle = {
|
|
1430
|
-
width: labelWidth,
|
|
1431
|
-
...(labelPos.toLowerCase() === 'right' ? { marginLeft: labelWidth, width: 'unset' } : {})
|
|
1432
|
-
};
|
|
1433
|
-
return (_jsx(FormFieldWrapper, { mode: mode, isActive: activeFieldId === child.id, component: child, onEdit: onEdit, onCopy: onCopy, onPaste: onPaste, onDelete: onDelete, onSwitchWidget: onSwitchWidget, isNested: true, index: index, totalCount: children.length, hasClipboardData: hasClipboardData, nestingContext: {
|
|
1434
|
-
parentId: component.id,
|
|
1435
|
-
parentPath: parentPath,
|
|
1436
|
-
cellInfo: undefined
|
|
1437
|
-
}, onNestedMoveUp: handleNestedMoveUp, onNestedMoveDown: handleNestedMoveDown, children: _jsxs(FormField, { name: child.id, children: [" ", _jsxs("div", { className: "panel-child-field", children: [_jsxs("div", { className: `field-content label-${labelPos.toLowerCase()}`, children: [!['checkbox', 'button', 'table', 'tabs', 'panel', 'message', 'card', 'splitButton', 'staticHtml'].includes(child.type) && child.label && !child.hideLabel && (_jsxs("label", { className: "field-label", htmlFor: child.id, style: {
|
|
1438
|
-
...labelStyle,
|
|
1439
|
-
display: 'flex',
|
|
1440
|
-
alignItems: 'center',
|
|
1441
|
-
gap: '6px',
|
|
1442
|
-
}, children: [_jsxs("span", { children: [child.label, child?.required && _jsx("span", { className: "required", children: "*" })] }), child?.tooltip && (_jsx(TooltipComponent, { content: child.tooltip, children: _jsx("span", { className: "e-msg-icon", style: {
|
|
1443
|
-
cursor: 'pointer',
|
|
1444
|
-
display: 'flex',
|
|
1445
|
-
alignItems: 'center',
|
|
1446
|
-
justifyContent: 'center',
|
|
1447
|
-
flexShrink: 0,
|
|
1448
|
-
} }) }))] })), _jsxs("div", { className: "field-input", inert: !['panel', 'table', 'tabs', 'card', 'staticHtml'].includes(child.type) ? true : undefined, children: [isLayoutComponent ? renderFormComponent({ ...child, parentPath: parentPath }) : renderFormComponent(child), formState?.errors[child.id] && (_jsx("div", { className: "e-error", children: formState?.errors[child.id] }))] })] }), child.description && _jsx("div", { className: 'field-description', children: child.description })] }, child.id)] }, `field-${child.id}`) }, child.id));
|
|
1449
|
-
}) })) }) }));
|
|
1450
|
-
};
|
|
1451
|
-
const renderFormComponent = (component) => {
|
|
1452
|
-
const isLayoutComponent = ['panel', 'table', 'tabs', 'card', 'staticHtml'].includes(component.type);
|
|
1453
|
-
if (isLayoutComponent) {
|
|
1454
|
-
switch (component.type) {
|
|
1455
|
-
case 'panel':
|
|
1456
|
-
const panelPath = component.parentPath ? `${component.parentPath}.${component.id}` : component.id;
|
|
1457
|
-
const panelChildren = component.children || [];
|
|
1458
|
-
const canReorderPanel = panelChildren.length > 0;
|
|
1459
|
-
return (_jsxs("fieldset", { className: "form-panel", style: { minInlineSize: 'unset' }, children: [!component.hideLabel && _jsx("legend", { className: "panel-legend", children: component.label || 'Panel' }), renderNestedChildren(component, panelChildren, panelPath, canReorderPanel)] }, component.id));
|
|
1460
|
-
case 'table':
|
|
1461
|
-
const tableRows = component.rows || 2;
|
|
1462
|
-
const tableCols = component.columns || 2;
|
|
1463
|
-
const tableCells = component.tableCells || [];
|
|
1464
|
-
const tablePath = component.parentPath ? `${component.parentPath}.${component.id}` : component.id;
|
|
1465
|
-
const defaultColSpan = Math.floor(12 / tableCols); // Calculate span: 1col=12, 2col=6, 3col=4, 4col=3
|
|
1466
|
-
const splittedWidths = component.columnWidths ? splitWidths(component.columnWidths.widths) : [];
|
|
1467
|
-
return (_jsx("div", { className: `form-table ${component.cssClass}`, children: _jsx("div", { className: "table-grid", children: Array.from({ length: tableRows }).map((_, rowIndex) => (_jsx("div", { className: "row table-row", children: Array.from({ length: tableCols }).map((_, colIndex) => {
|
|
1468
|
-
const cellComponents = tableCells[rowIndex]?.[colIndex] || [];
|
|
1469
|
-
const cellSpan = defaultColSpan; // Custom or default span
|
|
1470
|
-
const cellId = `${component.id}-cell-${rowIndex}-${colIndex}`;
|
|
1471
|
-
const cellPath = `${tablePath}.cell-${rowIndex}-${colIndex}`;
|
|
1472
|
-
const isDraggingOverCell = draggingOverPanel === cellId;
|
|
1473
|
-
const canReorderCell = cellComponents.length > 0;
|
|
1474
|
-
// Get width from columnWidths - applies to all rows if defined
|
|
1475
|
-
const colWidthValue = splittedWidths[colIndex];
|
|
1476
|
-
const cellStyle = colWidthValue
|
|
1477
|
-
? { width: `${colWidthValue}%` }
|
|
1478
|
-
: {};
|
|
1479
|
-
return (_jsx(Droppable, { scope: 'new-field', onOver: (args) => handleNestedDragOver(args, cellId), onOut: handleNestedDragLeave, onDrop: () => handleNestedDrop(component.id, { row: rowIndex, col: colIndex }, cellPath), children: _jsx("div", { "aria-label": 'Drop Zone', className: `col ${colWidthValue ? '' : !component.columnWidths || component.columnWidths.widths.every(w => w === null) ? `col-${cellSpan}` : ''} table-cell drop-zone ${isDraggingOverCell ? 'dragging-over' : ''}`, style: cellStyle, "data-container-id": component.id, "data-cell-row": rowIndex, "data-cell-col": colIndex, "data-cell-span": cellSpan, children: cellComponents.length === 0 ? (_jsx(Droppable, { scope: 'new-field', onOver: (args) => handleNestedDragOver(args, cellId), onOut: handleNestedDragLeave, onDrop: () => handleNestedDrop(component.id, { row: rowIndex, col: colIndex }, cellPath), children: _jsxs("p", { className: "drop-zone-text", children: [l10n.getConstant('cell'), " ", rowIndex + 1, "-", colIndex + 1] }) }, `panel-${isNestedReordering}`)) : canReorderCell ? (_jsx(Draggable, { scope: 'internal', clone: true, enableTailMode: true, isPreventSelect: true, helper: (args) => {
|
|
1480
|
-
nestedComponentsRef.current = cellComponents;
|
|
1481
|
-
nestedContainerIdRef.current = component.id;
|
|
1482
|
-
nestedContainerPathRef.current = cellPath;
|
|
1483
|
-
nestedCellInfoRef.current = { row: rowIndex, col: colIndex };
|
|
1484
|
-
setActiveNestedContainer(cellId);
|
|
1485
|
-
return nestedHelper(args, 'table-cell-child-draggable');
|
|
1486
|
-
}, onDragStart: nestedDragStart, onDragStop: (args) => nestedDragStop(args, 'table-cell-child-draggable'), onDrag: nestedDrag, children: _jsx("div", { style: { width: '100%', margin: '0px', padding: '0px' }, children: _jsx(Droppable, { scope: 'new-field', onOver: (args) => handleNestedDragOver(args, cellId), onOut: handleNestedDragLeave, onDrop: () => handleNestedDrop(component.id, { row: rowIndex, col: colIndex }, cellPath), children: _jsx("div", { className: `nested-components ${isNestedReordering && activeNestedContainer === cellId ? 'is-nested-reordering' : ''}`, children: cellComponents.map((nestedComp, index) => {
|
|
1487
|
-
const isLayoutComponent = ['panel', 'table', 'tabs', 'card', 'staticHtml'].includes(nestedComp.type);
|
|
1488
|
-
const labelPos = nestedComp.labelPosition || 'top';
|
|
1489
|
-
const labelWidth = nestedComp.labelWidth + '%' || 'auto';
|
|
1490
|
-
const labelStyle = {
|
|
1491
|
-
width: labelWidth,
|
|
1492
|
-
...(labelPos.toLowerCase() === 'right' ? { marginLeft: labelWidth, width: 'unset' } : {})
|
|
1493
|
-
};
|
|
1494
|
-
return (_jsx(FormFieldWrapper, { mode: mode, isActive: activeFieldId === nestedComp.id, component: nestedComp, onEdit: onEdit, onCopy: onCopy, onPaste: onPaste, onDelete: onDelete, onSwitchWidget: onSwitchWidget, isNested: true, index: index, totalCount: cellComponents.length, hasClipboardData: hasClipboardData, nestingContext: {
|
|
1495
|
-
parentId: component.id,
|
|
1496
|
-
parentPath: cellPath,
|
|
1497
|
-
cellInfo: { row: rowIndex, col: colIndex }
|
|
1498
|
-
}, onNestedMoveUp: handleNestedMoveUp, onNestedMoveDown: handleNestedMoveDown, children: _jsx(FormField, { name: nestedComp.id, children: _jsxs("div", { className: "nested-field table-cell-child-draggable", "data-nested-index": index, children: [_jsxs("div", { className: `field-content label-${labelPos.toLowerCase()}`, children: [!['checkbox', 'button', 'table', 'tabs', 'panel', 'message', 'card', 'splitButton', 'staticHtml'].includes(nestedComp.type) && nestedComp.label && !nestedComp.hideLabel && (_jsxs("label", { className: "field-label", htmlFor: nestedComp.id, style: {
|
|
1499
|
-
...labelStyle,
|
|
1500
|
-
display: 'flex',
|
|
1501
|
-
alignItems: 'center',
|
|
1502
|
-
gap: '6px',
|
|
1503
|
-
}, children: [_jsxs("span", { children: [nestedComp.label, nestedComp?.required && _jsx("span", { className: "required", children: "*" })] }), nestedComp?.tooltip && (_jsx(TooltipComponent, { content: nestedComp.tooltip, children: _jsx("span", { className: "e-msg-icon", style: {
|
|
1504
|
-
cursor: 'pointer',
|
|
1505
|
-
display: 'flex',
|
|
1506
|
-
alignItems: 'center',
|
|
1507
|
-
justifyContent: 'center',
|
|
1508
|
-
flexShrink: 0,
|
|
1509
|
-
} }) }))] })), _jsxs("div", { className: "nested-field-input", inert: !['panel', 'table', 'tabs', 'card', 'staticHtml'].includes(nestedComp.type) ? true : undefined, children: [isLayoutComponent ? renderFormComponent({ ...nestedComp, parentPath: cellPath }) : renderFormComponent(nestedComp), formState?.errors[nestedComp.id] && (_jsx("div", { className: "e-error", children: formState?.errors[nestedComp.id] }))] })] }), nestedComp.description && _jsx("div", { className: 'field-description', children: nestedComp.description })] }, `field-${nestedComp.id}`) }, `field-${nestedComp.id}`) }, nestedComp.id));
|
|
1510
|
-
}) }) }, `panel-${isNestedReordering}`) }) }, `draggable-cell-${component.id}-${rowIndex}-${colIndex}-${cellComponents.map(c => c.id).join('-')}`)) : (_jsx("div", { className: "nested-components", children: cellComponents.map((nestedComp, index) => {
|
|
1511
|
-
const isLayoutComponent = ['panel', 'table', 'tabs', 'card', 'staticHtml'].includes(nestedComp.type);
|
|
1512
|
-
const labelPos = nestedComp.labelPosition || 'top';
|
|
1513
|
-
const labelWidth = nestedComp.labelWidth + '%' || 'auto';
|
|
1514
|
-
const labelStyle = {
|
|
1515
|
-
width: labelWidth,
|
|
1516
|
-
...(labelPos.toLowerCase() === 'right' ? { marginLeft: labelWidth, width: 'unset' } : {})
|
|
1517
|
-
};
|
|
1518
|
-
return (_jsx(FormFieldWrapper, { mode: mode, isActive: activeFieldId === nestedComp.id, component: nestedComp, onEdit: onEdit, onCopy: onCopy, onPaste: onPaste, onDelete: onDelete, onSwitchWidget: onSwitchWidget, isNested: true, index: index, totalCount: cellComponents.length, hasClipboardData: hasClipboardData, nestingContext: {
|
|
1519
|
-
parentId: component.id,
|
|
1520
|
-
parentPath: cellPath,
|
|
1521
|
-
cellInfo: { row: rowIndex, col: colIndex }
|
|
1522
|
-
}, onNestedMoveUp: handleNestedMoveUp, onNestedMoveDown: handleNestedMoveDown, children: _jsx(FormField, { name: nestedComp.id, children: _jsxs("div", { className: "nested-field", children: [_jsxs("div", { className: `field-content label-${labelPos.toLowerCase()}`, children: [!['checkbox', 'button', 'table', 'tabs', 'panel', 'message', 'card', 'splitButton', 'staticHtml'].includes(nestedComp.type) && nestedComp.label && !nestedComp.hideLabel && (_jsxs("label", { className: "field-label", htmlFor: nestedComp.id, style: {
|
|
1523
|
-
...labelStyle,
|
|
1524
|
-
display: 'flex',
|
|
1525
|
-
alignItems: 'center',
|
|
1526
|
-
gap: '6px',
|
|
1527
|
-
}, children: [_jsxs("span", { children: [nestedComp.label, nestedComp?.required && _jsx("span", { className: "required", children: "*" })] }), nestedComp?.tooltip && (_jsx(TooltipComponent, { content: nestedComp.tooltip, children: _jsx("span", { className: "e-msg-icon", style: {
|
|
1528
|
-
cursor: 'pointer',
|
|
1529
|
-
display: 'flex',
|
|
1530
|
-
alignItems: 'center',
|
|
1531
|
-
justifyContent: 'center',
|
|
1532
|
-
flexShrink: 0,
|
|
1533
|
-
} }) }))] })), _jsxs("div", { className: "nested-field-input", inert: !['panel', 'table', 'tabs', 'card', 'staticHtml'].includes(nestedComp.type) ? true : undefined, children: [isLayoutComponent ? renderFormComponent({ ...nestedComp, parentPath: cellPath }) : renderFormComponent(nestedComp), formState?.errors[nestedComp.id] && (_jsx("div", { className: "e-error", children: formState?.errors[nestedComp.id] }))] })] }), nestedComp.description && _jsx("div", { className: 'field-description', children: nestedComp.description })] }, nestedComp.id) }, `field-${nestedComp.id}`) }, nestedComp.id));
|
|
1534
|
-
}) })) }, `cell-${rowIndex}-${colIndex}`) }, `cell-${rowIndex}-${colIndex}`));
|
|
1535
|
-
}) }, `row-${rowIndex}`))) }) }, component.id));
|
|
1536
|
-
case 'tabs': {
|
|
1537
|
-
const tabsPath = component.parentPath ? `${component.parentPath}.${component.id}` : component.id;
|
|
1538
|
-
const renderTabContent = (tabIndex) => {
|
|
1539
|
-
const tabId = `${component.id}-tab-${tabIndex}`;
|
|
1540
|
-
const isDraggingOverTab = draggingOverPanel === tabId;
|
|
1541
|
-
const tabContent = component.tabItems?.[tabIndex]?.content || [];
|
|
1542
|
-
const canReorderTab = tabContent.length > 0;
|
|
1543
|
-
const isDefaultOptions = !component.tabOptions;
|
|
1544
|
-
if (isDefaultOptions && component.tabItems) {
|
|
1545
|
-
component.tabItems[0].header = l10n.getConstant('defaultTabValue') || 'Tab 1';
|
|
1546
|
-
}
|
|
1547
|
-
return (_jsx(Droppable, { scope: 'new-field', onOver: (args) => handleNestedDragOver(args, tabId), onOut: handleNestedDragLeave, onDrop: () => handleNestedDrop(component.id, { row: 0, col: 0, tabIndex }, tabsPath), children: _jsx("div", { "aria-label": 'Drop Zone', className: `tab-drop-zone ${isDraggingOverTab ? 'dragging-over' : ''} ${tabContent.length === 0 ? 'empty' : ''}`, "data-container-id": component.id, "data-tab-index": tabIndex, children: tabContent.length === 0 ? (_jsx(Droppable, { scope: 'new-field', onOver: (args) => handleNestedDragOver(args, tabId), onOut: handleNestedDragLeave, onDrop: () => handleNestedDrop(component.id, { row: 0, col: 0, tabIndex }, tabsPath), children: _jsx("p", { className: "drop-zone-text", children: l10n.getConstant('emptyFieldSetState') }) }, `panel-${isNestedReordering}`)) : canReorderTab ? (_jsx(Draggable, { scope: 'internal', clone: true, enableTailMode: true, isPreventSelect: true, helper: (args) => {
|
|
1548
|
-
nestedComponentsRef.current = tabContent;
|
|
1549
|
-
nestedContainerIdRef.current = component.id;
|
|
1550
|
-
nestedContainerPathRef.current = tabsPath;
|
|
1551
|
-
nestedCellInfoRef.current = { tabIndex };
|
|
1552
|
-
setActiveNestedContainer(`${component.id}-tab-${tabIndex}`);
|
|
1553
|
-
return nestedHelper(args, 'tab-child-field-draggable');
|
|
1554
|
-
}, onDragStart: nestedDragStart, onDragStop: (args) => nestedDragStop(args, 'tab-child-field-draggable'), onDrag: nestedDrag, children: _jsx("div", { style: { width: '100%', margin: '0px', padding: '0px' }, children: _jsx(Droppable, { scope: 'new-field', onOver: (args) => handleNestedDragOver(args, tabId), onOut: handleNestedDragLeave, onDrop: () => handleNestedDrop(component.id, { row: 0, col: 0, tabIndex }, tabsPath), children: _jsx("div", { className: `tab-children ${isNestedReordering && activeNestedContainer === `${component.id}-tab-${tabIndex}` ? 'is-nested-reordering' : ''}`, children: tabContent.map((child, index) => {
|
|
1555
|
-
const isLayoutComponent = ['panel', 'table', 'tabs', 'card', 'staticHtml'].includes(child.type);
|
|
1556
|
-
const labelPos = child.labelPosition || 'top';
|
|
1557
|
-
const labelWidth = child.labelWidth + '%' || 'auto';
|
|
1558
|
-
const labelStyle = {
|
|
1559
|
-
width: labelWidth,
|
|
1560
|
-
...(labelPos.toLowerCase() === 'right' ? { marginLeft: labelWidth, width: 'unset' } : {})
|
|
1561
|
-
};
|
|
1562
|
-
return (_jsx(FormFieldWrapper, { mode: mode, isActive: activeFieldId === child.id, component: child, onEdit: onEdit, onCopy: onCopy, onPaste: onPaste, onDelete: onDelete, onSwitchWidget: onSwitchWidget, isNested: true, index: index, totalCount: tabContent.length, hasClipboardData: hasClipboardData, nestingContext: {
|
|
1563
|
-
parentId: component.id,
|
|
1564
|
-
parentPath: tabsPath,
|
|
1565
|
-
cellInfo: { tabIndex }
|
|
1566
|
-
}, onNestedMoveUp: handleNestedMoveUp, onNestedMoveDown: handleNestedMoveDown, children: _jsx(FormField, { name: child.id, children: _jsxs("div", { className: "tab-child-field tab-child-field-draggable", "data-nested-index": index, children: [_jsxs("div", { className: `field-content label-${labelPos.toLowerCase()}`, children: [!['checkbox', 'button', 'table', 'tabs', 'panel', 'message', 'card', 'splitButton', 'staticHtml'].includes(child.type) && child.label && !child.hideLabel && (_jsxs("label", { className: "field-label", htmlFor: child.id, style: {
|
|
1567
|
-
...labelStyle,
|
|
1568
|
-
display: 'flex',
|
|
1569
|
-
alignItems: 'center',
|
|
1570
|
-
gap: '6px',
|
|
1571
|
-
}, children: [_jsxs("span", { children: [child.label, child?.required && _jsx("span", { className: "required", children: "*" })] }), child?.tooltip && (_jsx(TooltipComponent, { content: child.tooltip, children: _jsx("span", { className: "e-msg-icon", style: {
|
|
1572
|
-
cursor: 'pointer',
|
|
1573
|
-
display: 'flex',
|
|
1574
|
-
alignItems: 'center',
|
|
1575
|
-
justifyContent: 'center',
|
|
1576
|
-
flexShrink: 0,
|
|
1577
|
-
} }) }))] })), _jsxs("div", { className: "field-input", inert: !['panel', 'table', 'tabs', 'card', 'staticHtml'].includes(child.type) ? true : undefined, children: [isLayoutComponent ? renderFormComponent({ ...child, parentPath: tabsPath }) : renderFormComponent(child), formState?.errors[child.id] && (_jsx("div", { className: "e-error", children: formState?.errors[child.id] }))] })] }), child.description && _jsx("div", { className: 'field-description', children: child.description })] }, `field-${child.id}`) }, `field-${child.id}`) }, child.id));
|
|
1578
|
-
}) }) }, `panel-${isNestedReordering}`) }) }, `draggable-tab-${component.id}-${tabIndex}-${tabContent.map(c => c.id).join('-')}`)) : (_jsx("div", { className: "tab-children", children: tabContent.map((child, index) => {
|
|
1579
|
-
const isLayoutComponent = ['panel', 'table', 'tabs', 'card', 'staticHtml'].includes(child.type);
|
|
1580
|
-
const labelPos = child.labelPosition || 'top';
|
|
1581
|
-
const labelWidth = child.labelWidth + '%' || 'auto';
|
|
1582
|
-
const labelStyle = {
|
|
1583
|
-
width: labelWidth,
|
|
1584
|
-
...(labelPos.toLowerCase() === 'right' ? { marginLeft: labelWidth, width: 'unset' } : {})
|
|
1585
|
-
};
|
|
1586
|
-
return (_jsx(FormFieldWrapper, { mode: mode, isActive: activeFieldId === child.id, component: child, onEdit: onEdit, onCopy: onCopy, onPaste: onPaste, onDelete: onDelete, onSwitchWidget: onSwitchWidget, isNested: true, index: index, totalCount: tabContent.length, hasClipboardData: hasClipboardData, nestingContext: {
|
|
1587
|
-
parentId: component.id,
|
|
1588
|
-
parentPath: tabsPath,
|
|
1589
|
-
cellInfo: { tabIndex }
|
|
1590
|
-
}, onNestedMoveUp: handleNestedMoveUp, onNestedMoveDown: handleNestedMoveDown, children: _jsx(FormField, { name: child.id, children: _jsxs("div", { className: "tab-child-field", children: [_jsxs("div", { className: `field-content label-${labelPos.toLowerCase()}`, children: [!['checkbox', 'button', 'table', 'tabs', 'panel', 'message', 'card', 'splitButton', 'staticHtml'].includes(child.type) && child.label && !child.hideLabel && (_jsxs("label", { className: "field-label", htmlFor: child.id, style: {
|
|
1591
|
-
...labelStyle,
|
|
1592
|
-
display: 'flex',
|
|
1593
|
-
alignItems: 'center',
|
|
1594
|
-
gap: '6px',
|
|
1595
|
-
}, children: [_jsxs("span", { children: [child.label, child?.required && _jsx("span", { className: "required", children: "*" })] }), child?.tooltip && (_jsxs(TooltipComponent, { content: child.tooltip, children: [_jsx("span", { className: "e-msg-icon", style: {
|
|
1596
|
-
cursor: 'pointer',
|
|
1597
|
-
display: 'flex',
|
|
1598
|
-
alignItems: 'center',
|
|
1599
|
-
justifyContent: 'center',
|
|
1600
|
-
flexShrink: 0,
|
|
1601
|
-
} }), "0"] }))] })), _jsxs("div", { className: "field-input", inert: !['panel', 'table', 'tabs', 'card', 'staticHtml'].includes(child.type) ? true : undefined, children: [isLayoutComponent ? renderFormComponent({ ...child, parentPath: tabsPath }) : renderFormComponent(child), formState?.errors[child.id] && (_jsx("div", { className: "e-error", children: formState?.errors[child.id] }))] })] }), child.description && _jsx("div", { className: 'field-description', children: child.description })] }, child.id) }, `field-${child.id}`) }, child.id));
|
|
1602
|
-
}) })) }) }));
|
|
1603
|
-
};
|
|
1604
|
-
return (_jsx(CustomTabComponent, { className: component.cssClass || '', tabs: component.tabItems?.map((tab, tabIndex) => ({
|
|
1605
|
-
header: tab.header,
|
|
1606
|
-
content: renderTabContent(tabIndex)
|
|
1607
|
-
})) || [], selectedIndex: activeTabIndices[component.id] || 0, onTabSelect: (index) => {
|
|
1608
|
-
setActiveTabIndices(prev => ({
|
|
1609
|
-
...prev,
|
|
1610
|
-
[component.id]: index
|
|
1611
|
-
}));
|
|
1612
|
-
} }, `${component.id}`));
|
|
1613
|
-
}
|
|
1614
|
-
case 'card': {
|
|
1615
|
-
const cardPath = component.parentPath ? `${component.parentPath}.${component.id}` : component.id;
|
|
1616
|
-
const cardChildren = component.children || [];
|
|
1617
|
-
const canReorderCard = cardChildren.length > 0;
|
|
1618
|
-
return (_jsxs("fieldset", { className: `e-card form-card ${component.cssClass}`, style: { padding: 0, minInlineSize: 'unset' }, children: [_jsx("div", { className: "e-card-header", children: _jsxs("div", { className: "e-card-header-caption", children: [_jsx("div", { className: "e-card-title", children: component.cardTitle || l10n.getConstant('cardTitle') }), component.cardSubtitle && _jsx("div", { className: "e-card-sub-title", children: component.cardSubtitle })] }) }), _jsx("div", { className: "e-card-content", children: renderNestedChildren(component, cardChildren, cardPath, canReorderCard) })] }, component.id));
|
|
1619
|
-
}
|
|
1620
|
-
case 'staticHtml':
|
|
1621
|
-
return (_jsx(StaticHtmlRenderer, { id: component.id, content: component.defaultValue || '<p>HTML content</p>', enableHtmlSanitizer: component.enableHtmlSanitizer }, component.id));
|
|
1622
|
-
default:
|
|
1623
|
-
return null;
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
else {
|
|
1627
|
-
return _jsx(RenderComponent, { component: component, formState: formState, gridRefs: gridRefsRef.current, l10n: l10n, locale: locale, formSettings: formSchema.settings });
|
|
1628
|
-
}
|
|
1629
|
-
};
|
|
1630
|
-
// Generate split schema and combined schema for export
|
|
1631
|
-
const splitData = React.useMemo(() => splitSchema(formSchema), [formSchema]);
|
|
1632
|
-
useEffect(() => {
|
|
1633
|
-
setFormId(null);
|
|
1634
|
-
}, [splitData, formSchema]);
|
|
1635
|
-
const copyCombinedSchema = () => {
|
|
1636
|
-
navigator.clipboard.writeText(JSON.stringify(splitData, null, 2));
|
|
1637
|
-
};
|
|
1638
|
-
const uploadToServer = async () => {
|
|
1639
|
-
try {
|
|
1640
|
-
const res = await fetch(/*builderSubmitUrl*/ '', {
|
|
1641
|
-
method: 'POST',
|
|
1642
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1643
|
-
body: JSON.stringify(splitData)
|
|
1644
|
-
});
|
|
1645
|
-
if (!res.ok) {
|
|
1646
|
-
const text = await res.text().catch(() => '');
|
|
1647
|
-
console.error('FormRenderer: submitUrl POST failed', res.status, text);
|
|
1648
|
-
try {
|
|
1649
|
-
setFormId(null);
|
|
1650
|
-
}
|
|
1651
|
-
catch (e) { /* ignore */ }
|
|
1652
|
-
return { ok: false, status: res.status, body: text };
|
|
1653
|
-
}
|
|
1654
|
-
const contentType = (res.headers.get('Content-Type') || '').toLowerCase();
|
|
1655
|
-
let data = null;
|
|
1656
|
-
if (contentType.includes('application/json')) {
|
|
1657
|
-
data = await res.json();
|
|
1658
|
-
const returnedId = data?.id ?? null;
|
|
1659
|
-
if (returnedId) {
|
|
1660
|
-
try {
|
|
1661
|
-
setFormId(String(returnedId));
|
|
1662
|
-
}
|
|
1663
|
-
catch (e) {
|
|
1664
|
-
console.error('setFormId error', e);
|
|
1665
|
-
}
|
|
1666
|
-
let formId = getFormId();
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
else if (contentType.startsWith('text/')) {
|
|
1670
|
-
data = await res.text();
|
|
1671
|
-
try {
|
|
1672
|
-
const parsed = JSON.parse(data || '{}');
|
|
1673
|
-
const returnedId = parsed?.id ?? parsed?.formId ?? null;
|
|
1674
|
-
if (returnedId) {
|
|
1675
|
-
try {
|
|
1676
|
-
setFormId(String(returnedId));
|
|
1677
|
-
}
|
|
1678
|
-
catch (e) {
|
|
1679
|
-
console.error('setFormId error', e);
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
catch (e) {
|
|
1684
|
-
// not JSON, ignore
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
else {
|
|
1688
|
-
data = await res.blob();
|
|
1689
|
-
}
|
|
1690
|
-
return { ok: true, status: res.status, data, formId: getFormId() };
|
|
1691
|
-
}
|
|
1692
|
-
catch (err) {
|
|
1693
|
-
try {
|
|
1694
|
-
setFormId(null);
|
|
1695
|
-
}
|
|
1696
|
-
catch (e) { /* ignore */ }
|
|
1697
|
-
return { ok: false, error: err };
|
|
1698
|
-
}
|
|
1699
|
-
};
|
|
1700
|
-
const handleSubmit = () => {
|
|
1701
|
-
};
|
|
1702
|
-
return (_jsxs("main", { className: `main-section ${className || ''}`, children: [_jsx("div", { className: "main-content fb-scrollbar", children: !showSplitView ? (_jsxs("div", { className: `form-preview-area ${isDraggingOver ? 'dragging-over' : ''} ${formSchema.components.length === 0 || (formSchema.components.length === 1 && formSchema.components[0].buttonType === 'submit') ? 'empty' : ''} ${isReordering ? 'is-reordering' : ''} ${isNestedReordering ? 'is-nesteditemreordering' : ''}`, tabIndex: -1, role: "region", "aria-label": "Form Preview", children: [(formSchema.components.length === 0 || (formSchema.components.length === 1 && formSchema.components[0].buttonType === 'submit')) ? (_jsx("div", { className: "empty-state", style: { width: formSchema.settings?.width || 'var(--form-width)' }, children: _jsx(Droppable, { scope: 'new-field', onOver: (args) => handleDragOver(args), onOut: handleDragLeave, onDrop: handleDrop, children: _jsx("div", { className: "form-dropzone", "aria-label": 'Drop Zone', children: _jsxs("div", { className: "form-dropzone-content", children: [_jsx(FormDropZoneIcon, {}), isDraggingOver ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "dropzone-title", children: l10n.getConstant('dropToAddField') }), _jsx("p", { className: "dropzone-hint", children: l10n.getConstant('releaseToAddField') })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "dropzone-title", children: l10n.getConstant('startBuildingForm') }), _jsx("p", { className: "dropzone-hint", children: l10n.getConstant('dragFieldsHint') })] }))] }) }) }) })) : (_jsx(Droppable, { scope: 'new-field', onOver: (args) => handleDragOver(args), onOut: handleDragLeave, onDrop: handleDrop, children: _jsxs("div", { style: { margin: "0 auto", minHeight: "100%" }, children: [_jsx(Form, { rules: validationRules, onSubmit: handleSubmit, onFormStateChange: setFormState, initialValues: initialValues, validateOnChange: true, "aria-label": "Form Builder Preview", autoComplete: 'off', style: { minWidth: formSchema.settings?.width || 'var(--form-width)', maxWidth: formSchema.settings?.width || 'var(--form-width)', overflowX: 'auto' }, className: "form-components", children: _jsx(Draggable, { scope: 'internal', clone: true, enableTailMode: true, isPreventSelect: true, helper: helper, onDragStart: (e) => dragStart(e), onDragStop: dragStop, onDrag: (e) => drag(e), children: _jsxs("div", { style: { width: '100%', margin: '0px', padding: '0px' }, children: [_jsx(Droppable, { scope: 'new-field', onOver: handleDragOver, onOut: handleDragLeave, onDrop: handleDrop, children: _jsx("div", { className: `draggable-fields-container`, children: formSchema.components.map((component, index) => {
|
|
1703
|
-
const labelPos = component.labelPosition || 'top';
|
|
1704
|
-
const labelWidth = component.labelWidth + '%' || 'auto';
|
|
1705
|
-
const labelStyle = {
|
|
1706
|
-
width: labelWidth,
|
|
1707
|
-
...(labelPos.toLowerCase() === 'right' ? { marginLeft: labelWidth, width: 'unset' } : {})
|
|
1708
|
-
};
|
|
1709
|
-
return (_jsx(FormFieldWrapper, { mode: mode, isActive: activeFieldId === component.id, component: component, onEdit: onEdit, onCopy: onCopy, onPaste: onPaste, onDelete: onDelete, onSwitchWidget: onSwitchWidget, onMoveUp: handleMoveUp, onMoveDown: handleMoveDown, index: index, totalCount: formSchema.components.length, hasClipboardData: hasClipboardData, children: _jsx(FormField, { name: component.id, children: _jsxs("div", { className: "form-field form-field-draggable", "data-index": index, "data-component-id": component.id, children: [_jsxs("div", { className: `field-content label-${labelPos.toLowerCase()} ${['panel', 'table', 'tabs', 'card', 'staticHtml'].includes(component.type) ? 'layout-component' : ''}`, children: [!['checkbox', 'button', 'table', 'tabs', 'panel', 'message', 'card', 'splitButton', 'staticHtml'].includes(component.type) && component.label && !component.hideLabel && (_jsxs("label", { className: "field-label", htmlFor: component.id, style: {
|
|
1710
|
-
...labelStyle,
|
|
1711
|
-
display: 'flex',
|
|
1712
|
-
alignItems: 'center',
|
|
1713
|
-
gap: '6px',
|
|
1714
|
-
}, children: [_jsxs("span", { children: [component.label, component?.required && _jsx("span", { className: "required", children: "*" })] }), component?.tooltip && (_jsx(TooltipComponent, { content: component.tooltip, children: _jsx("span", { className: "e-msg-icon", "aria-label": component.tooltip, style: {
|
|
1715
|
-
cursor: 'pointer',
|
|
1716
|
-
display: 'flex',
|
|
1717
|
-
alignItems: 'center',
|
|
1718
|
-
justifyContent: 'center',
|
|
1719
|
-
flexShrink: 0,
|
|
1720
|
-
} }) }))] })), _jsxs("div", { className: "field-input", inert: !['panel', 'table', 'tabs', 'card', 'staticHtml'].includes(component.type) ? true : undefined, children: [renderFormComponent(component), formState?.errors[component.id] && (_jsx("div", { className: "e-error", role: "alert", "aria-live": "assertive", children: formState?.errors[component.id] }))] })] }), component.description && _jsx("div", { className: 'field-description', children: component.description })] }) }, `field-${component.id}`) }, component.id));
|
|
1721
|
-
}) }) }), _jsx("div", { style: { marginTop: '1rem' }, className: "add-field-message-wrapper", tabIndex: 0, onKeyDown: (e) => {
|
|
1722
|
-
if (e.code === "Enter") {
|
|
1723
|
-
setIsListBoxPaneOpen(true);
|
|
1724
|
-
}
|
|
1725
|
-
}, "aria-label": "Add new field", children: _jsxs(MessageComponent, { style: { justifyContent: 'center', cursor: 'pointer', fontSize: '12px', lineHeight: '16px' }, showIcon: false, showCloseIcon: false, cssClass: "add-field-message", "aria-label": "Add new field", onClick: () => setIsListBoxPaneOpen(true), children: [_jsx("span", { "aria-hidden": "true", style: { position: 'relative', top: '2px', marginRight: '10px' }, className: 'e-icons e-plus' }), _jsx("span", { children: "Drag any field here or click to add new field" })] }) })] }) }, `draggable-${formSchema.components.map(c => c.id).join('-')}`) }), _jsx(ListBoxPane, { isOpen: isListBoxPaneOpen, onClose: () => setIsListBoxPaneOpen(false), onSelect: handleListBoxItemSelect, paletteConfig: paletteConfig, mode: currentMode })] }) })), showClone && (_jsx("div", { ref: cloneHelperRef, className: "form-field-clone", "aria-hidden": true, style: {
|
|
1726
|
-
opacity: '0',
|
|
1727
|
-
padding: '12px',
|
|
1728
|
-
zIndex: '10000',
|
|
1729
|
-
justifyContent: 'center',
|
|
1730
|
-
alignItems: 'center',
|
|
1731
|
-
gap: '8px',
|
|
1732
|
-
height: '37px',
|
|
1733
|
-
minWidth: '170px',
|
|
1734
|
-
backgroundColor: 'rgb(var(--sf-drag-clone))',
|
|
1735
|
-
border: '2px solid var(--sf-color-primary)',
|
|
1736
|
-
borderRadius: '8px',
|
|
1737
|
-
boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
|
|
1738
|
-
pointerEvents: 'none',
|
|
1739
|
-
display: 'none',
|
|
1740
|
-
}, children: _jsx("div", { className: "clone-content", style: { fontSize: '14px', fontWeight: '500' }, children: draggedItemIndex.current !== null && currentComponentsRef.current[draggedItemIndex.current]?.label }) })), _jsx("div", { ref: nestedCloneHelperRef, className: "nested-field-clone", "aria-hidden": true, style: {
|
|
1741
|
-
opacity: isNestedReordering ? '1' : '0',
|
|
1742
|
-
padding: '12px',
|
|
1743
|
-
zIndex: '10001',
|
|
1744
|
-
justifyContent: 'center',
|
|
1745
|
-
alignItems: 'center',
|
|
1746
|
-
gap: '8px',
|
|
1747
|
-
height: '37px',
|
|
1748
|
-
minWidth: '170px',
|
|
1749
|
-
backgroundColor: 'var(--sf-color-surface)',
|
|
1750
|
-
border: '2px solid var(--sf-color-primary)',
|
|
1751
|
-
borderRadius: '8px',
|
|
1752
|
-
boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
|
|
1753
|
-
pointerEvents: 'none',
|
|
1754
|
-
display: isNestedReordering ? 'flex' : 'none',
|
|
1755
|
-
position: 'fixed',
|
|
1756
|
-
}, children: _jsx("div", { className: "clone-content", style: { fontSize: '14px', fontWeight: '500' }, children: nestedDraggedIndex.current !== null && (nestedComponentsRef.current[nestedDraggedIndex.current]?.label ||
|
|
1757
|
-
nestedComponentsRef.current[nestedDraggedIndex.current]?.legend ||
|
|
1758
|
-
nestedComponentsRef.current[nestedDraggedIndex.current]?.type) }) })] })) : (_jsx("div", { className: "split-view-container", children: _jsx("div", { className: "split-panels", children: _jsxs("div", { className: "split-panel full-width", "aria-labelledby": "combined-schema-heading", children: [_jsxs("div", { className: "json-header", children: [_jsx("span", { className: "json-title", children: l10n.getConstant('combinedSchema') || 'Schema' }), !isDefaultFormSchema(formSchema) &&
|
|
1759
|
-
_jsx(ButtonComponent, { iconCss: "e-icons e-copy", onClick: copyCombinedSchema, "aria-label": l10n.getConstant('copy'), children: l10n.getConstant('copy') })] }), _jsx("pre", { className: "json-content", role: "textbox", "aria-readonly": "true", "aria-label": 'Schema', children: JSON.stringify(isDefaultFormSchema(formSchema) ? {} : splitData, null, 2) })] }) }) })) }), _jsx(Preview, { isOpen: isPreviewOpen, onClose: () => setIsPreviewOpen(false), schema: splitData, formSchema: formSchema }), _jsx("div", { id: "Editor-Tooltip", role: "toolbar", "aria-label": "Form Builder Actions", className: `editor-handle ${className}`, style: isPreviewOpen ? { zIndex: 99999 } : { zIndex: 999 }, children: _jsx(ToolbarComponent, { "aria-label": "Editor Actions", tabIndex: 0, id: 'toolbar', style: { width: '352px' }, children: _jsxs(ItemsDirective, { children: [!showSplitView && !isPreviewOpen &&
|
|
1760
|
-
_jsx(ItemDirective, { prefixIcon: 'e-icons e-copy', click: copyCombinedSchema, disabled: isDefaultFormSchema(formSchema), tooltipText: l10n.getConstant('copyCode') }), !showSplitView && !isPreviewOpen &&
|
|
1761
|
-
_jsx(ItemDirective, { prefixIcon: 'e-icons e-trash', click: () => setFormSchema(defaultFormSchema), disabled: isDefaultFormSchema(formSchema), tooltipText: l10n.getConstant('resetForm') }), !showSplitView && !isPreviewOpen &&
|
|
1762
|
-
_jsx(ItemDirective, { type: "Separator" }), _jsx(ItemDirective, { prefixIcon: 'e-icons e-edit', text: showSplitView || isPreviewOpen ? "Build" : undefined, click: () => { setShowSplitView(false); setIsPreviewOpen(false); }, cssClass: !showSplitView && !isPreviewOpen ? 'e-toolbar-active' : '', tooltipText: l10n.getConstant('editForm') }), _jsx(ItemDirective, { prefixIcon: 'e-icons e-code-view', text: showSplitView || isPreviewOpen ? "JSON" : undefined, click: () => { setShowSplitView(true); setIsPreviewOpen(false); }, cssClass: showSplitView && !isPreviewOpen ? 'e-toolbar-active' : '', tooltipText: l10n.getConstant('jsonview') }), enablePreview &&
|
|
1763
|
-
_jsx(ItemDirective, { prefixIcon: 'e-icons e-eye', text: showSplitView || isPreviewOpen ? "Preview" : undefined, click: () => { /*uploadToServer()*/ ; setIsPreviewOpen(true); }, cssClass: isPreviewOpen ? 'e-toolbar-active' : '', disabled: isDefaultFormSchema(formSchema), tooltipText: l10n.getConstant('previewForm') })] }) }) })] }, currentMode));
|
|
1764
|
-
});
|
|
1765
|
-
MainSection.displayName = 'MainSection';
|
|
1766
|
-
export default MainSection;
|