@syncfusion/ej2-react-form-builder 0.1.0-beta → 35.1.42

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.
Files changed (160) hide show
  1. package/README.md +81 -36
  2. package/diConfig.json +148 -0
  3. package/dist/ej2-react-form-builder.min.js +10 -0
  4. package/dist/ej2-react-form-builder.umd.min.js +11 -0
  5. package/dist/ej2-react-form-builder.umd.min.js.map +1 -0
  6. package/dist/es6/ej2-react-form-builder.es2015.js +60 -0
  7. package/dist/es6/ej2-react-form-builder.es2015.js.map +1 -0
  8. package/form-builder.d.ts +4 -0
  9. package/form-builder.js +4 -0
  10. package/index.d.ts +2 -4
  11. package/index.js +2 -4
  12. package/license +6 -13
  13. package/package.json +19 -43
  14. package/src/form-builder/formbuilder.component.d.ts +37 -0
  15. package/src/form-builder/formbuilder.component.js +50 -0
  16. package/src/form-builder/index.d.ts +2 -0
  17. package/src/form-builder/index.js +2 -0
  18. package/src/form-builder/toolboxitems-directive.d.ts +28 -0
  19. package/src/form-builder/toolboxitems-directive.js +47 -0
  20. package/src/global.js +1 -0
  21. package/src/index.d.ts +2 -16
  22. package/src/index.js +2 -6
  23. package/Common/CheckboxGroup.d.ts +0 -13
  24. package/Common/CheckboxGroup.js +0 -50
  25. package/Common/ConditionalRuleEngine.d.ts +0 -296
  26. package/Common/ConditionalRuleEngine.js +0 -566
  27. package/Common/CustomTabComponent.d.ts +0 -17
  28. package/Common/CustomTabComponent.js +0 -30
  29. package/Common/CustomValidationEngine.d.ts +0 -58
  30. package/Common/CustomValidationEngine.js +0 -146
  31. package/Common/DataGridComponent.d.ts +0 -20
  32. package/Common/DataGridComponent.js +0 -273
  33. package/Common/FormValidator.d.ts +0 -485
  34. package/Common/FormValidator.js +0 -575
  35. package/Common/MinMaxRuleEngine.d.ts +0 -105
  36. package/Common/MinMaxRuleEngine.js +0 -365
  37. package/Common/StaticHtmlRenderer.d.ts +0 -14
  38. package/Common/StaticHtmlRenderer.js +0 -24
  39. package/Common/expressions/DependencyTracker.d.ts +0 -30
  40. package/Common/expressions/DependencyTracker.js +0 -128
  41. package/Common/expressions/ExpressionEngine.d.ts +0 -95
  42. package/Common/expressions/ExpressionEngine.js +0 -506
  43. package/Common/expressions/ExpressionParser.d.ts +0 -41
  44. package/Common/expressions/ExpressionParser.js +0 -269
  45. package/Common/expressions/useGridExpressions.d.ts +0 -12
  46. package/Common/expressions/useGridExpressions.js +0 -71
  47. package/Common/globalFormId.d.ts +0 -26
  48. package/Common/globalFormId.js +0 -45
  49. package/Common/json-converter.d.ts +0 -444
  50. package/Common/json-converter.js +0 -1184
  51. package/Common/parser.d.ts +0 -98
  52. package/Common/parser.js +0 -753
  53. package/Common/utils.d.ts +0 -130
  54. package/Common/utils.js +0 -574
  55. package/src/FormBuilder.d.ts +0 -211
  56. package/src/FormBuilder.js +0 -1299
  57. package/src/components/Database/ConnectionForm.d.ts +0 -8
  58. package/src/components/Database/ConnectionForm.js +0 -28
  59. package/src/components/TopBar.d.ts +0 -10
  60. package/src/components/TopBar.js +0 -7
  61. package/src/components/icons/ComponentIcons.d.ts +0 -44
  62. package/src/components/icons/ComponentIcons.js +0 -40
  63. package/src/components/layout/FormFieldWrapper.d.ts +0 -41
  64. package/src/components/layout/FormFieldWrapper.js +0 -112
  65. package/src/components/layout/LeftPane.d.ts +0 -23
  66. package/src/components/layout/LeftPane.js +0 -310
  67. package/src/components/layout/ListBoxPane.d.ts +0 -24
  68. package/src/components/layout/ListBoxPane.js +0 -94
  69. package/src/components/layout/MainSection.d.ts +0 -39
  70. package/src/components/layout/MainSection.js +0 -1766
  71. package/src/components/layout/Preview.d.ts +0 -12
  72. package/src/components/layout/Preview.js +0 -337
  73. package/src/components/layout/RightPane.d.ts +0 -22
  74. package/src/components/layout/RightPane.js +0 -370
  75. package/src/components/theme/ThemeSwitcher.d.ts +0 -14
  76. package/src/components/theme/ThemeSwitcher.js +0 -24
  77. package/src/components/theme/theme-constant.d.ts +0 -37
  78. package/src/components/theme/theme-constant.js +0 -23
  79. package/src/config/component-properties.json +0 -4929
  80. package/src/config/form-setting.json +0 -56
  81. package/src/config/locale.d.ts +0 -449
  82. package/src/config/locale.js +0 -449
  83. package/src/context/FormSchemaContext.d.ts +0 -32
  84. package/src/context/FormSchemaContext.js +0 -155
  85. package/src/context/LocalizationContext.d.ts +0 -27
  86. package/src/context/LocalizationContext.js +0 -43
  87. package/src/context/ThemeContext.d.ts +0 -23
  88. package/src/context/ThemeContext.js +0 -161
  89. package/src/context/contexts.d.ts +0 -6
  90. package/src/context/contexts.js +0 -6
  91. package/src/context/defaultFormSchema.d.ts +0 -6
  92. package/src/context/defaultFormSchema.js +0 -20
  93. package/src/defaultPalette.d.ts +0 -9
  94. package/src/defaultPalette.js +0 -45
  95. package/src/defaultTemplate.d.ts +0 -3
  96. package/src/defaultTemplate.js +0 -185
  97. package/src/import/schemaImportUtils.d.ts +0 -24
  98. package/src/import/schemaImportUtils.js +0 -121
  99. package/src/types/ActionButtons.d.ts +0 -44
  100. package/src/types/ActionButtons.js +0 -27
  101. package/src/types/ComponentProperties.d.ts +0 -33
  102. package/src/types/ComponentProperties.js +0 -2
  103. package/src/types/FormSchema.d.ts +0 -585
  104. package/src/types/FormSchema.js +0 -147
  105. package/src/types/connection.d.ts +0 -19
  106. package/src/types/connection.js +0 -9
  107. package/src/utils/AttributesEditor.d.ts +0 -13
  108. package/src/utils/AttributesEditor.js +0 -117
  109. package/src/utils/ButtonGroupEditor.d.ts +0 -15
  110. package/src/utils/ButtonGroupEditor.js +0 -141
  111. package/src/utils/ChoiceBasedFieldEditor.d.ts +0 -16
  112. package/src/utils/ChoiceBasedFieldEditor.js +0 -87
  113. package/src/utils/ClipboardHelper.d.ts +0 -26
  114. package/src/utils/ClipboardHelper.js +0 -83
  115. package/src/utils/ColumnBuilder.d.ts +0 -11
  116. package/src/utils/ColumnBuilder.js +0 -153
  117. package/src/utils/ColumnSpanEditor.d.ts +0 -8
  118. package/src/utils/ColumnSpanEditor.js +0 -98
  119. package/src/utils/ConditionDataEditor.d.ts +0 -29
  120. package/src/utils/ConditionDataEditor.js +0 -177
  121. package/src/utils/ConditionalEditorUtils.d.ts +0 -27
  122. package/src/utils/ConditionalEditorUtils.js +0 -310
  123. package/src/utils/ConditionsEditor.d.ts +0 -20
  124. package/src/utils/ConditionsEditor.js +0 -571
  125. package/src/utils/CustomAccordion.d.ts +0 -13
  126. package/src/utils/CustomAccordion.js +0 -38
  127. package/src/utils/CustomValidationEditor.d.ts +0 -11
  128. package/src/utils/CustomValidationEditor.js +0 -208
  129. package/src/utils/DataSourceCollector.d.ts +0 -3
  130. package/src/utils/DataSourceCollector.js +0 -252
  131. package/src/utils/ExpressionEditor.d.ts +0 -13
  132. package/src/utils/ExpressionEditor.js +0 -99
  133. package/src/utils/FormSettingsPanel.d.ts +0 -9
  134. package/src/utils/FormSettingsPanel.js +0 -60
  135. package/src/utils/LookupConfig.d.ts +0 -11
  136. package/src/utils/LookupConfig.js +0 -159
  137. package/src/utils/MinMaxValueEditor.d.ts +0 -25
  138. package/src/utils/MinMaxValueEditor.js +0 -188
  139. package/src/utils/OptionsEditor.d.ts +0 -8
  140. package/src/utils/OptionsEditor.js +0 -79
  141. package/src/utils/PropertyRenderer.d.ts +0 -22
  142. package/src/utils/PropertyRenderer.js +0 -210
  143. package/src/utils/ValueRenderer.d.ts +0 -2
  144. package/src/utils/ValueRenderer.js +0 -91
  145. package/src/utils/ej2DbAdaptor.d.ts +0 -6
  146. package/src/utils/ej2DbAdaptor.js +0 -38
  147. package/src/utils/mappingHeuristics.d.ts +0 -10
  148. package/src/utils/mappingHeuristics.js +0 -33
  149. package/src/utils/renderComponent.d.ts +0 -18
  150. package/src/utils/renderComponent.js +0 -300
  151. package/styles/tailwind3.css +0 -67
  152. package/styles/themes/action-buttons.css +0 -110
  153. package/styles/themes/datagrid.css +0 -380
  154. package/styles/themes/drag-reorder.css +0 -248
  155. package/styles/themes/import-dialog.css +0 -292
  156. package/styles/themes/index.css +0 -658
  157. package/styles/themes/left-pane.css +0 -253
  158. package/styles/themes/options-editor.css +0 -63
  159. package/styles/themes/preview.css +0 -702
  160. 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;