@syncfusion/ej2-react-form-builder 0.1.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Common/CheckboxGroup.d.ts +13 -0
- package/Common/CheckboxGroup.js +50 -0
- package/Common/ConditionalRuleEngine.d.ts +296 -0
- package/Common/ConditionalRuleEngine.js +566 -0
- package/Common/CustomTabComponent.d.ts +17 -0
- package/Common/CustomTabComponent.js +30 -0
- package/Common/CustomValidationEngine.d.ts +58 -0
- package/Common/CustomValidationEngine.js +146 -0
- package/Common/DataGridComponent.d.ts +20 -0
- package/Common/DataGridComponent.js +273 -0
- package/Common/FormValidator.d.ts +485 -0
- package/Common/FormValidator.js +575 -0
- package/Common/MinMaxRuleEngine.d.ts +105 -0
- package/Common/MinMaxRuleEngine.js +365 -0
- package/Common/StaticHtmlRenderer.d.ts +14 -0
- package/Common/StaticHtmlRenderer.js +24 -0
- package/Common/expressions/DependencyTracker.d.ts +30 -0
- package/Common/expressions/DependencyTracker.js +128 -0
- package/Common/expressions/ExpressionEngine.d.ts +95 -0
- package/Common/expressions/ExpressionEngine.js +506 -0
- package/Common/expressions/ExpressionParser.d.ts +41 -0
- package/Common/expressions/ExpressionParser.js +269 -0
- package/Common/expressions/useGridExpressions.d.ts +12 -0
- package/Common/expressions/useGridExpressions.js +71 -0
- package/Common/globalFormId.d.ts +26 -0
- package/Common/globalFormId.js +45 -0
- package/Common/json-converter.d.ts +444 -0
- package/Common/json-converter.js +1184 -0
- package/Common/parser.d.ts +98 -0
- package/Common/parser.js +753 -0
- package/Common/utils.d.ts +130 -0
- package/Common/utils.js +574 -0
- package/README.md +53 -0
- package/index.d.ts +6 -0
- package/index.js +6 -0
- package/license +13 -0
- package/package.json +54 -0
- package/src/FormBuilder.d.ts +211 -0
- package/src/FormBuilder.js +1299 -0
- package/src/components/Database/ConnectionForm.d.ts +8 -0
- package/src/components/Database/ConnectionForm.js +28 -0
- package/src/components/TopBar.d.ts +10 -0
- package/src/components/TopBar.js +7 -0
- package/src/components/icons/ComponentIcons.d.ts +44 -0
- package/src/components/icons/ComponentIcons.js +40 -0
- package/src/components/layout/FormFieldWrapper.d.ts +41 -0
- package/src/components/layout/FormFieldWrapper.js +112 -0
- package/src/components/layout/LeftPane.d.ts +23 -0
- package/src/components/layout/LeftPane.js +310 -0
- package/src/components/layout/ListBoxPane.d.ts +24 -0
- package/src/components/layout/ListBoxPane.js +94 -0
- package/src/components/layout/MainSection.d.ts +39 -0
- package/src/components/layout/MainSection.js +1766 -0
- package/src/components/layout/Preview.d.ts +12 -0
- package/src/components/layout/Preview.js +337 -0
- package/src/components/layout/RightPane.d.ts +22 -0
- package/src/components/layout/RightPane.js +370 -0
- package/src/components/theme/ThemeSwitcher.d.ts +14 -0
- package/src/components/theme/ThemeSwitcher.js +24 -0
- package/src/components/theme/theme-constant.d.ts +37 -0
- package/src/components/theme/theme-constant.js +23 -0
- package/src/config/component-properties.json +4929 -0
- package/src/config/form-setting.json +56 -0
- package/src/config/locale.d.ts +449 -0
- package/src/config/locale.js +449 -0
- package/src/context/FormSchemaContext.d.ts +32 -0
- package/src/context/FormSchemaContext.js +155 -0
- package/src/context/LocalizationContext.d.ts +27 -0
- package/src/context/LocalizationContext.js +43 -0
- package/src/context/ThemeContext.d.ts +23 -0
- package/src/context/ThemeContext.js +161 -0
- package/src/context/contexts.d.ts +6 -0
- package/src/context/contexts.js +6 -0
- package/src/context/defaultFormSchema.d.ts +6 -0
- package/src/context/defaultFormSchema.js +20 -0
- package/src/defaultPalette.d.ts +9 -0
- package/src/defaultPalette.js +45 -0
- package/src/defaultTemplate.d.ts +3 -0
- package/src/defaultTemplate.js +185 -0
- package/src/import/schemaImportUtils.d.ts +24 -0
- package/src/import/schemaImportUtils.js +121 -0
- package/src/index.d.ts +16 -0
- package/src/index.js +6 -0
- package/src/types/ActionButtons.d.ts +44 -0
- package/src/types/ActionButtons.js +27 -0
- package/src/types/ComponentProperties.d.ts +33 -0
- package/src/types/ComponentProperties.js +2 -0
- package/src/types/FormSchema.d.ts +585 -0
- package/src/types/FormSchema.js +147 -0
- package/src/types/connection.d.ts +19 -0
- package/src/types/connection.js +9 -0
- package/src/utils/AttributesEditor.d.ts +13 -0
- package/src/utils/AttributesEditor.js +117 -0
- package/src/utils/ButtonGroupEditor.d.ts +15 -0
- package/src/utils/ButtonGroupEditor.js +141 -0
- package/src/utils/ChoiceBasedFieldEditor.d.ts +16 -0
- package/src/utils/ChoiceBasedFieldEditor.js +87 -0
- package/src/utils/ClipboardHelper.d.ts +26 -0
- package/src/utils/ClipboardHelper.js +83 -0
- package/src/utils/ColumnBuilder.d.ts +11 -0
- package/src/utils/ColumnBuilder.js +153 -0
- package/src/utils/ColumnSpanEditor.d.ts +8 -0
- package/src/utils/ColumnSpanEditor.js +98 -0
- package/src/utils/ConditionDataEditor.d.ts +29 -0
- package/src/utils/ConditionDataEditor.js +177 -0
- package/src/utils/ConditionalEditorUtils.d.ts +27 -0
- package/src/utils/ConditionalEditorUtils.js +310 -0
- package/src/utils/ConditionsEditor.d.ts +20 -0
- package/src/utils/ConditionsEditor.js +571 -0
- package/src/utils/CustomAccordion.d.ts +13 -0
- package/src/utils/CustomAccordion.js +38 -0
- package/src/utils/CustomValidationEditor.d.ts +11 -0
- package/src/utils/CustomValidationEditor.js +208 -0
- package/src/utils/DataSourceCollector.d.ts +3 -0
- package/src/utils/DataSourceCollector.js +252 -0
- package/src/utils/ExpressionEditor.d.ts +13 -0
- package/src/utils/ExpressionEditor.js +99 -0
- package/src/utils/FormSettingsPanel.d.ts +9 -0
- package/src/utils/FormSettingsPanel.js +60 -0
- package/src/utils/LookupConfig.d.ts +11 -0
- package/src/utils/LookupConfig.js +159 -0
- package/src/utils/MinMaxValueEditor.d.ts +25 -0
- package/src/utils/MinMaxValueEditor.js +188 -0
- package/src/utils/OptionsEditor.d.ts +8 -0
- package/src/utils/OptionsEditor.js +79 -0
- package/src/utils/PropertyRenderer.d.ts +22 -0
- package/src/utils/PropertyRenderer.js +210 -0
- package/src/utils/ValueRenderer.d.ts +2 -0
- package/src/utils/ValueRenderer.js +91 -0
- package/src/utils/ej2DbAdaptor.d.ts +6 -0
- package/src/utils/ej2DbAdaptor.js +38 -0
- package/src/utils/mappingHeuristics.d.ts +10 -0
- package/src/utils/mappingHeuristics.js +33 -0
- package/src/utils/renderComponent.d.ts +18 -0
- package/src/utils/renderComponent.js +300 -0
- package/styles/tailwind3.css +67 -0
- package/styles/themes/action-buttons.css +110 -0
- package/styles/themes/datagrid.css +380 -0
- package/styles/themes/drag-reorder.css +248 -0
- package/styles/themes/import-dialog.css +292 -0
- package/styles/themes/index.css +658 -0
- package/styles/themes/left-pane.css +253 -0
- package/styles/themes/options-editor.css +63 -0
- package/styles/themes/preview.css +702 -0
- package/styles/themes/property-dialog.css +371 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState, useMemo, useCallback } from 'react';
|
|
3
|
+
import { Draggable } from '@syncfusion/react-base';
|
|
4
|
+
import { AccordionComponent, AccordionItemsDirective, AccordionItemDirective } from '@syncfusion/ej2-react-navigations';
|
|
5
|
+
import { ComponentCategory } from '../../types/FormSchema';
|
|
6
|
+
import { defaultPalette } from '../../defaultPalette';
|
|
7
|
+
import { generateId, generateName } from '../../../Common/json-converter';
|
|
8
|
+
import { TextBoxIcon, TextAreaIcon, NumericIcon, EmailIcon, UrlIcon, PhoneIcon, PasswordIcon, CheckboxIcon, CheckboxGroupIcon, RadioIcon, DropdownIcon, MultiSelectIcon, ButtonIcon, DatePickerIcon, DateTimePickerIcon, TimePickerIcon, DateRangeIcon, SwitchIcon, SignatureIcon, RatingIcon, ImageEditorIcon, FileUploadIcon, RangeSliderIcon, ColorPickerIcon, InputMaskIcon, MessageIcon, PanelIcon, TableIcon, DataGridIcon, TabsIcon, TemplateIcon, CardIcon, SplitButtonIcon, RichTextEditorIcon, StaticHtmlIcon, LookupIcon, OrderNumberIcon } from '../icons/ComponentIcons';
|
|
9
|
+
import { useLocalization } from '../../context/LocalizationContext';
|
|
10
|
+
export const floatFocus = (args) => {
|
|
11
|
+
args.target.parentElement.classList.add("e-input-focus");
|
|
12
|
+
};
|
|
13
|
+
export const floatBlur = (args) => {
|
|
14
|
+
args.target.parentElement.classList.remove('e-input-focus');
|
|
15
|
+
};
|
|
16
|
+
export const getIconComponent = (type) => {
|
|
17
|
+
switch (type) {
|
|
18
|
+
case 'textbox':
|
|
19
|
+
return _jsx(TextBoxIcon, { className: "palette-item-icon-svg" });
|
|
20
|
+
case 'textarea':
|
|
21
|
+
return _jsx(TextAreaIcon, { className: "palette-item-icon-svg" });
|
|
22
|
+
case 'number':
|
|
23
|
+
return _jsx(NumericIcon, { className: "palette-item-icon-svg" });
|
|
24
|
+
case 'email':
|
|
25
|
+
return _jsx(EmailIcon, { className: "palette-item-icon-svg" });
|
|
26
|
+
case 'url':
|
|
27
|
+
return _jsx(UrlIcon, { className: "palette-item-icon-svg" });
|
|
28
|
+
case 'phone':
|
|
29
|
+
return _jsx(PhoneIcon, { className: "palette-item-icon-svg" });
|
|
30
|
+
case 'password':
|
|
31
|
+
return _jsx(PasswordIcon, { className: "palette-item-icon-svg" });
|
|
32
|
+
case 'checkbox':
|
|
33
|
+
return _jsx(CheckboxIcon, { className: "palette-item-icon-svg" });
|
|
34
|
+
case 'checkboxGroup':
|
|
35
|
+
return _jsx(CheckboxGroupIcon, { className: "palette-item-icon-svg" });
|
|
36
|
+
case 'radio':
|
|
37
|
+
return _jsx(RadioIcon, { className: "palette-item-icon-svg" });
|
|
38
|
+
case 'dropdown':
|
|
39
|
+
return _jsx(DropdownIcon, { className: "palette-item-icon-svg" });
|
|
40
|
+
case 'multiselect':
|
|
41
|
+
return _jsx(MultiSelectIcon, { className: "palette-item-icon-svg" });
|
|
42
|
+
case 'button':
|
|
43
|
+
return _jsx(ButtonIcon, { className: "palette-item-icon-svg" });
|
|
44
|
+
case 'date':
|
|
45
|
+
return _jsx(DatePickerIcon, { className: "palette-item-icon-svg" });
|
|
46
|
+
case 'dateTime':
|
|
47
|
+
return _jsx(DateTimePickerIcon, { className: "palette-item-icon-svg" });
|
|
48
|
+
case 'time':
|
|
49
|
+
return _jsx(TimePickerIcon, { className: "palette-item-icon-svg" });
|
|
50
|
+
case 'dateRange':
|
|
51
|
+
return _jsx(DateRangeIcon, { className: "palette-item-icon-svg" });
|
|
52
|
+
case 'switch':
|
|
53
|
+
return _jsx(SwitchIcon, { className: "palette-item-icon-svg" });
|
|
54
|
+
case 'signature':
|
|
55
|
+
return _jsx(SignatureIcon, { className: "palette-item-icon-svg" });
|
|
56
|
+
case 'rating':
|
|
57
|
+
return _jsx(RatingIcon, { className: "palette-item-icon-svg" });
|
|
58
|
+
case 'imageEditor':
|
|
59
|
+
return _jsx(ImageEditorIcon, { className: "palette-item-icon-svg" });
|
|
60
|
+
case 'fileUpload':
|
|
61
|
+
return _jsx(FileUploadIcon, { className: "palette-item-icon-svg" });
|
|
62
|
+
case 'rangeSlider':
|
|
63
|
+
return _jsx(RangeSliderIcon, { className: "palette-item-icon-svg" });
|
|
64
|
+
case 'colorPicker':
|
|
65
|
+
return _jsx(ColorPickerIcon, { className: "palette-item-icon-svg" });
|
|
66
|
+
case 'inputMask':
|
|
67
|
+
return _jsx(InputMaskIcon, { className: "palette-item-icon-svg" });
|
|
68
|
+
case 'message':
|
|
69
|
+
return _jsx(MessageIcon, { className: "palette-item-icon-svg" });
|
|
70
|
+
case 'panel':
|
|
71
|
+
return _jsx(PanelIcon, { className: "palette-item-icon-svg" });
|
|
72
|
+
case 'table':
|
|
73
|
+
return _jsx(TableIcon, { className: "palette-item-icon-svg" });
|
|
74
|
+
case 'dataGrid':
|
|
75
|
+
return _jsx(DataGridIcon, { className: "palette-item-icon-svg" });
|
|
76
|
+
case 'tabs':
|
|
77
|
+
return _jsx(TabsIcon, { className: "palette-item-icon-svg" });
|
|
78
|
+
case 'card':
|
|
79
|
+
return _jsx(CardIcon, { className: "palette-item-icon-svg" });
|
|
80
|
+
case 'splitButton':
|
|
81
|
+
return _jsx(SplitButtonIcon, { className: "palette-item-icon-svg" });
|
|
82
|
+
case 'richTextEditor':
|
|
83
|
+
return _jsx(RichTextEditorIcon, { className: "palette-item-icon-svg" });
|
|
84
|
+
case 'lookup':
|
|
85
|
+
return _jsx(LookupIcon, { className: "palette-item-icon-svg" });
|
|
86
|
+
case 'staticHtml':
|
|
87
|
+
return _jsx(StaticHtmlIcon, { className: "palette-item-icon-svg" });
|
|
88
|
+
case 'autoNumber':
|
|
89
|
+
return _jsx(OrderNumberIcon, { className: "palette-item-icon-svg" });
|
|
90
|
+
default:
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
export const simpleModeHiddenTypes = new Set([
|
|
95
|
+
'splitButton',
|
|
96
|
+
'imageEditor',
|
|
97
|
+
'dataGrid',
|
|
98
|
+
'richTextEditor',
|
|
99
|
+
'lookup',
|
|
100
|
+
'autoNumber',
|
|
101
|
+
'colorPicker',
|
|
102
|
+
'signature'
|
|
103
|
+
]);
|
|
104
|
+
const LeftPane = ({ className, templates = [], paletteConfig, onFieldDragStarted, mode = 'Simple', onDrop, containerWidth = 1024 }) => {
|
|
105
|
+
const cloneHelperRef = useRef(null);
|
|
106
|
+
const draggedComponentRef = useRef(null);
|
|
107
|
+
const { l10n } = useLocalization();
|
|
108
|
+
const [searchInput, setSearchInput] = useState('');
|
|
109
|
+
const [showSplitView, setShowSplitView] = useState(false);
|
|
110
|
+
const [templateSearchInput, setTemplateSearchInput] = useState('');
|
|
111
|
+
const isMobile = containerWidth <= 820;
|
|
112
|
+
// Initialize palette with user config or defaults, resolve labels internally from l10n
|
|
113
|
+
const palette = useMemo(() => {
|
|
114
|
+
const merged = { ...defaultPalette };
|
|
115
|
+
if (paletteConfig) {
|
|
116
|
+
Object.entries(paletteConfig).forEach(([category, components]) => {
|
|
117
|
+
if (components) {
|
|
118
|
+
merged[category] = components;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
// Filter out advanced components when in Simple mode
|
|
123
|
+
if (mode === 'Simple' && merged[ComponentCategory.ADVANCED]) {
|
|
124
|
+
merged[ComponentCategory.ADVANCED] = merged[ComponentCategory.ADVANCED].filter(comp => !simpleModeHiddenTypes.has(comp.type));
|
|
125
|
+
}
|
|
126
|
+
// Resolve labels internally from l10n for all components
|
|
127
|
+
const withLabels = {};
|
|
128
|
+
Object.entries(merged).forEach(([category, components]) => {
|
|
129
|
+
withLabels[category] = (components || []).map(comp => ({
|
|
130
|
+
...comp,
|
|
131
|
+
label: comp.label || l10n.getConstant(comp.type) || comp.type
|
|
132
|
+
}));
|
|
133
|
+
});
|
|
134
|
+
return withLabels;
|
|
135
|
+
}, [paletteConfig, l10n, mode, simpleModeHiddenTypes]); // ADD mode dependencies
|
|
136
|
+
// Get label from component or resolve from l10n
|
|
137
|
+
const getComponentLabel = (component) => {
|
|
138
|
+
if (component.label)
|
|
139
|
+
return component.label;
|
|
140
|
+
return l10n.getConstant(component.type) || component.type;
|
|
141
|
+
};
|
|
142
|
+
// Mobile click-to-add handler: builds a FormComponent and drops it at the bottom of the canvas
|
|
143
|
+
const handlePaletteItemClick = useCallback((component) => {
|
|
144
|
+
if (!isMobile || !onDrop)
|
|
145
|
+
return;
|
|
146
|
+
const newComponent = {
|
|
147
|
+
id: generateId(component.type),
|
|
148
|
+
type: component.type,
|
|
149
|
+
label: component.label || '',
|
|
150
|
+
name: generateName(component.type),
|
|
151
|
+
};
|
|
152
|
+
// Append to bottom of canvas (default behavior, smart submit-button handling is applied downstream)
|
|
153
|
+
onDrop(newComponent);
|
|
154
|
+
}, [isMobile, onDrop]);
|
|
155
|
+
// Mobile keydown handler: builds a FormComponent and drops it at the bottom of the canvas
|
|
156
|
+
const handlePaletteItemKeyDown = useCallback((e, component) => {
|
|
157
|
+
if (!onDrop || e.code !== 'Enter')
|
|
158
|
+
return;
|
|
159
|
+
const newComponent = {
|
|
160
|
+
id: generateId(component.type),
|
|
161
|
+
type: component.type,
|
|
162
|
+
label: component.label || '',
|
|
163
|
+
name: generateName(component.type),
|
|
164
|
+
};
|
|
165
|
+
// Append to bottom of canvas (default behavior, smart submit-button handling is applied downstream)
|
|
166
|
+
onDrop(newComponent);
|
|
167
|
+
}, [onDrop]);
|
|
168
|
+
// Extract components from palette (labels already resolved)
|
|
169
|
+
const basicComponents = useMemo(() => palette[ComponentCategory.BASIC] || [], [palette]);
|
|
170
|
+
const isBlockedInSimpleMode = (type) => {
|
|
171
|
+
return mode === 'Simple' && [
|
|
172
|
+
'splitButton', 'imageEditor', 'dataGrid', 'richTextEditor',
|
|
173
|
+
'lookup', 'autoNumber', 'colorPicker', 'signature'
|
|
174
|
+
].includes(type);
|
|
175
|
+
};
|
|
176
|
+
const advancedComponents = useMemo(() => (palette[ComponentCategory.ADVANCED] || []).filter(c => !isBlockedInSimpleMode(c.type)), [palette, mode]);
|
|
177
|
+
const layoutComponents = useMemo(() => palette[ComponentCategory.LAYOUT] || [], [palette]);
|
|
178
|
+
const allComponents = useMemo(() => [
|
|
179
|
+
...basicComponents,
|
|
180
|
+
...advancedComponents,
|
|
181
|
+
...layoutComponents
|
|
182
|
+
], [basicComponents, advancedComponents, layoutComponents]);
|
|
183
|
+
const filteredComponents = useMemo(() => {
|
|
184
|
+
if (!searchInput.trim()) {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
const lowerSearch = searchInput.toLowerCase();
|
|
188
|
+
return allComponents.filter(component => component.label?.toLowerCase().includes(lowerSearch) ||
|
|
189
|
+
component.type.toLowerCase().includes(lowerSearch));
|
|
190
|
+
}, [searchInput, allComponents]);
|
|
191
|
+
const filteredTemplates = useMemo(() => {
|
|
192
|
+
if (!templateSearchInput.trim()) {
|
|
193
|
+
return templates;
|
|
194
|
+
}
|
|
195
|
+
const lowerSearch = templateSearchInput.toLowerCase();
|
|
196
|
+
return templates.filter(t => (l10n.getConstant(t.id) || t.title)?.toLowerCase().includes(lowerSearch) ||
|
|
197
|
+
t.id.toLowerCase().includes(lowerSearch));
|
|
198
|
+
}, [templateSearchInput, templates, l10n]);
|
|
199
|
+
// Syncfusion Draggable handlers
|
|
200
|
+
const helper = (args) => {
|
|
201
|
+
const paletteItem = (args.sender?.target).closest('.palette-item');
|
|
202
|
+
if (!paletteItem)
|
|
203
|
+
return null;
|
|
204
|
+
const templateId = paletteItem.getAttribute('data-template-id');
|
|
205
|
+
if (templateId && templates.length) {
|
|
206
|
+
const tpl = templates.find(t => t.id === templateId);
|
|
207
|
+
if (tpl) {
|
|
208
|
+
draggedComponentRef.current = { type: 'template', label: (l10n.getConstant(tpl.id) || tpl.title), isTemplate: true, templateData: tpl };
|
|
209
|
+
return cloneHelperRef.current;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const componentType = paletteItem.getAttribute('data-component-type');
|
|
213
|
+
const component = allComponents.find(c => c.type === componentType);
|
|
214
|
+
if (component) {
|
|
215
|
+
draggedComponentRef.current = component;
|
|
216
|
+
}
|
|
217
|
+
return cloneHelperRef.current;
|
|
218
|
+
};
|
|
219
|
+
const syncDragStart = (args) => {
|
|
220
|
+
if (isMobile) {
|
|
221
|
+
args.cancel = true;
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (cloneHelperRef.current && draggedComponentRef.current) {
|
|
225
|
+
cloneHelperRef.current.style.display = 'flex';
|
|
226
|
+
// If dragging a template, serialize template JSON into the clone as `data-template-json`
|
|
227
|
+
if (draggedComponentRef.current.isTemplate) {
|
|
228
|
+
const tpl = draggedComponentRef.current.templateData;
|
|
229
|
+
try {
|
|
230
|
+
cloneHelperRef.current.setAttribute('data-template-json', JSON.stringify(tpl));
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
}
|
|
234
|
+
cloneHelperRef.current.removeAttribute('data-component-json');
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
cloneHelperRef.current.setAttribute('data-component-json', JSON.stringify(draggedComponentRef.current));
|
|
238
|
+
}
|
|
239
|
+
const iconSpan = cloneHelperRef.current.querySelector('.palette-item-icon');
|
|
240
|
+
if (draggedComponentRef.current.isTemplate && iconSpan) {
|
|
241
|
+
iconSpan.innerHTML = '';
|
|
242
|
+
}
|
|
243
|
+
const labelSpan = cloneHelperRef.current.querySelector('.clone-label');
|
|
244
|
+
const paletteItem = document.querySelector(`.palette-item[data-component-type="${draggedComponentRef.current.type}"]`);
|
|
245
|
+
if (iconSpan && paletteItem) {
|
|
246
|
+
const sourceIcon = paletteItem.querySelector('.palette-item-icon');
|
|
247
|
+
if (sourceIcon) {
|
|
248
|
+
iconSpan.innerHTML = sourceIcon.innerHTML;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (labelSpan) {
|
|
252
|
+
labelSpan.textContent = draggedComponentRef.current.label;
|
|
253
|
+
}
|
|
254
|
+
// Trigger onFieldDragStarted event for palette drags
|
|
255
|
+
if (onFieldDragStarted && draggedComponentRef.current) {
|
|
256
|
+
const isTemplate = !!draggedComponentRef.current.isTemplate;
|
|
257
|
+
onFieldDragStarted({
|
|
258
|
+
componentName: null, // Palette items don't have IDs yet
|
|
259
|
+
componentType: isTemplate ? 'template' : draggedComponentRef.current.type,
|
|
260
|
+
sourceContainerId: 'palette',
|
|
261
|
+
isNested: false
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
const syncDrag = () => {
|
|
267
|
+
if (cloneHelperRef.current) {
|
|
268
|
+
cloneHelperRef.current.style.opacity = '0.97';
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
const syncDragStop = () => {
|
|
272
|
+
setTimeout(() => {
|
|
273
|
+
if (cloneHelperRef.current) {
|
|
274
|
+
cloneHelperRef.current.style.display = 'none';
|
|
275
|
+
cloneHelperRef.current.style.opacity = '0';
|
|
276
|
+
cloneHelperRef.current.removeAttribute('data-component-json');
|
|
277
|
+
cloneHelperRef.current.removeAttribute('data-template-json');
|
|
278
|
+
}
|
|
279
|
+
}, 100);
|
|
280
|
+
draggedComponentRef.current = null;
|
|
281
|
+
};
|
|
282
|
+
const handleTabKeyDown = useCallback((e, showTemplates) => {
|
|
283
|
+
if (e.code === 'Enter') {
|
|
284
|
+
e.preventDefault();
|
|
285
|
+
setShowSplitView(showTemplates);
|
|
286
|
+
}
|
|
287
|
+
}, []);
|
|
288
|
+
return (_jsxs("aside", { "aria-label": l10n.getConstant('fields'), role: "navigation", className: `left-pane ${className || ''}`, style: containerWidth <= 820 ? { position: 'absolute', zIndex: 1000 } : {}, children: [_jsx("div", { className: "sidebar-header", children: _jsxs("div", { style: { display: 'flex', gap: '30px' }, role: "tablist", "aria-label": "Builder Navigation", children: [_jsx("div", { role: "tab", "aria-selected": !showSplitView, "aria-labelledby": "fields-panel", className: `tab ${!showSplitView && mode === 'Developer' ? 'active' : ''}`, style: { cursor: 'pointer' }, onClick: () => setShowSplitView(false), onKeyDown: (e) => handleTabKeyDown(e, false), tabIndex: 0, children: _jsx("p", { children: l10n.getConstant('fields') }) }), mode === 'Developer' && !isMobile &&
|
|
289
|
+
_jsx("div", { className: `tab ${showSplitView ? 'active' : ''}`, style: { cursor: 'pointer' }, onClick: () => setShowSplitView(true), onKeyDown: (e) => handleTabKeyDown(e, true), tabIndex: 0, role: "tab", "aria-selected": showSplitView, "aria-labelledby": "templates-panel", children: _jsx("p", { children: l10n.getConstant('templates') }) })] }) }), _jsxs("div", { className: "palette-container fb-scrollbar", children: [_jsxs("div", { className: "e-input-group", style: { marginBottom: "10px" }, children: [_jsx("input", { className: "e-input", type: "text", "aria-label": l10n.getConstant('search'), placeholder: l10n.getConstant('search'), value: showSplitView ? templateSearchInput : searchInput, onFocus: floatFocus, onBlur: floatBlur, onChange: (e) => showSplitView ? setTemplateSearchInput(e.target.value) : setSearchInput(e.target.value) }), _jsx("span", { "aria-hidden": "true", className: "e-input-group-icon e-icons e-search" })] }), !showSplitView && (_jsx(Draggable, { scope: 'new-field', clone: true, enableTailMode: true, helper: helper, isPreventSelect: true, onDragStart: syncDragStart, onDrag: syncDrag, onDragStop: syncDragStop, children: searchInput.trim() ? (
|
|
290
|
+
// Render filtered field items when search is active
|
|
291
|
+
_jsx("div", { role: "tabpanel", "aria-labelledby": "fields-tab", className: "palette-items", style: { paddingTop: '8px' }, children: filteredComponents.length > 0 ? (filteredComponents.map((component) => (_jsxs("div", { className: "palette-item", tabIndex: 0, "data-component-type": component.type, style: { userSelect: 'none', WebkitUserSelect: 'none' }, onClick: () => handlePaletteItemClick(component), onKeyDown: (e) => handlePaletteItemKeyDown(e, component), "aria-label": getComponentLabel(component), children: [_jsx("span", { "aria-hidden": "true", className: "palette-item-icon", children: getIconComponent(component.type) }), _jsx("span", { className: "palette-item-label", children: getComponentLabel(component) })] }, component.type)))) : (_jsx("div", { role: "status", "aria-live": "polite", style: { padding: '16px', textAlign: 'center', color: 'var(--sf-text-secondary)' }, children: "No results found" })) })) : (
|
|
292
|
+
// Render accordion when search is empty
|
|
293
|
+
_jsx(AccordionComponent, { expandMode: "Single", children: _jsxs(AccordionItemsDirective, { children: [_jsx(AccordionItemDirective, { header: l10n.getConstant('basic'), content: "#leftpane-basic", expanded: true }), _jsx(AccordionItemDirective, { header: l10n.getConstant('advanced'), content: "#leftpane-advanced" }), _jsx(AccordionItemDirective, { header: l10n.getConstant('layout'), content: "#leftpane-layout" })] }) })) })), showSplitView && (_jsx(Draggable, { scope: 'new-field', clone: true, enableTailMode: true, helper: helper, isPreventSelect: true, onDragStart: syncDragStart, onDrag: syncDrag, onDragStop: syncDragStop, children: _jsx("div", { role: "tabpanel", "aria-labelledby": "templates-tab", className: "palette-items", style: { paddingTop: '8px' }, children: filteredTemplates.length > 0 ? (filteredTemplates.map(t => (_jsxs("div", { tabIndex: 0, className: "palette-item template-item", "data-template-id": t.id, style: { userSelect: 'none', WebkitUserSelect: 'none' }, children: [_jsx("span", { className: "palette-item-icon", children: _jsx(TemplateIcon, { className: "palette-item-icon-svg" }) }), _jsx("span", { className: "palette-item-label", children: (l10n.getConstant(t.id) || t.title) })] }, t.id)))) : (_jsx("div", { role: "status", "aria-live": "polite", style: { padding: '16px', textAlign: 'center', color: 'var(--sf-text-secondary)' }, children: "No templates found" })) }) })), _jsxs("div", { style: { display: 'none' }, "aria-hidden": true, children: [_jsx("div", { id: "leftpane-basic", children: _jsx("div", { className: "palette-items", children: basicComponents.map((component) => (_jsxs("div", { tabIndex: 0, className: "palette-item", "data-component-type": component.type, style: { userSelect: 'none', WebkitUserSelect: 'none' }, onClick: () => handlePaletteItemClick(component), onKeyDown: (e) => handlePaletteItemKeyDown(e, component), "aria-label": getComponentLabel(component), children: [_jsx("span", { "aria-hidden": "true", className: "palette-item-icon", children: getIconComponent(component.type) }), _jsx("span", { className: "palette-item-label", children: getComponentLabel(component) })] }, component.type))) }) }), _jsx("div", { id: "leftpane-advanced", children: _jsx("div", { className: "palette-items", children: advancedComponents.map((component) => (_jsxs("div", { tabIndex: 0, className: "palette-item", "data-component-type": component.type, style: { userSelect: 'none', WebkitUserSelect: 'none' }, onClick: () => handlePaletteItemClick(component), onKeyDown: (e) => handlePaletteItemKeyDown(e, component), "aria-label": getComponentLabel(component), children: [_jsx("span", { "aria-hidden": "true", className: "palette-item-icon", children: getIconComponent(component.type) }), _jsx("span", { className: "palette-item-label", children: getComponentLabel(component) })] }, component.type))) }) }), _jsx("div", { id: "leftpane-layout", children: _jsx("div", { className: "palette-items", children: layoutComponents.map((component) => (_jsxs("div", { tabIndex: 0, className: "palette-item", "data-component-type": component.type, style: { userSelect: 'none', WebkitUserSelect: 'none' }, onClick: () => handlePaletteItemClick(component), onKeyDown: (e) => handlePaletteItemKeyDown(e, component), "aria-label": getComponentLabel(component), children: [_jsx("span", { "aria-hidden": "true", className: "palette-item-icon", children: getIconComponent(component.type) }), _jsx("span", { className: "palette-item-label", children: getComponentLabel(component) })] }, component.type))) }) })] }), _jsxs("div", { ref: cloneHelperRef, className: "palette-item-clone", "aria-hidden": "true", style: {
|
|
294
|
+
opacity: '0',
|
|
295
|
+
padding: '8px 12px',
|
|
296
|
+
zIndex: '10000',
|
|
297
|
+
display: 'none',
|
|
298
|
+
justifyContent: 'center',
|
|
299
|
+
alignItems: 'center',
|
|
300
|
+
gap: '8px',
|
|
301
|
+
height: '37px',
|
|
302
|
+
minWidth: '120px',
|
|
303
|
+
backgroundColor: 'rgb(var(--sf-drag-clone))',
|
|
304
|
+
border: '2px solid var(--sf-color-primary)',
|
|
305
|
+
borderRadius: '8px',
|
|
306
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
|
|
307
|
+
pointerEvents: 'none',
|
|
308
|
+
}, children: [_jsx("span", { "aria-hidden": "true", className: "palette-item-icon", style: { fontSize: '16px' } }), _jsx("span", { className: "clone-label", style: { fontSize: '14px', fontWeight: '500' } })] })] })] }));
|
|
309
|
+
};
|
|
310
|
+
export default LeftPane;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type PaletteComponent, type PaletteConfig } from '../../types/FormSchema';
|
|
3
|
+
interface ListBoxPaneProps {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
onSelect: (component: PaletteComponent) => void;
|
|
7
|
+
paletteConfig?: PaletteConfig;
|
|
8
|
+
mode?: 'Simple' | 'Developer';
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* ListBoxPane - Syncfusion ListBox grouped list popup for quick field addition
|
|
12
|
+
*
|
|
13
|
+
* Features:
|
|
14
|
+
* - Syncfusion ListBox with native grouping
|
|
15
|
+
* - Searchable via filter
|
|
16
|
+
* - Click outside to close
|
|
17
|
+
* - Escape key to close
|
|
18
|
+
* - Component selection triggers onSelect callback
|
|
19
|
+
*
|
|
20
|
+
* @param props - Component props
|
|
21
|
+
* @returns Popup component with Syncfusion ListBox
|
|
22
|
+
*/
|
|
23
|
+
declare const ListBoxPane: React.FC<ListBoxPaneProps>;
|
|
24
|
+
export default ListBoxPane;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useMemo, useCallback } from 'react';
|
|
3
|
+
import { ListBoxComponent } from '@syncfusion/ej2-react-dropdowns';
|
|
4
|
+
import { defaultPalette } from '../../defaultPalette';
|
|
5
|
+
import { useLocalization } from '../../context/LocalizationContext';
|
|
6
|
+
import { getIconComponent, simpleModeHiddenTypes } from './LeftPane';
|
|
7
|
+
import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
|
|
8
|
+
/**
|
|
9
|
+
* ListBoxPane - Syncfusion ListBox grouped list popup for quick field addition
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Syncfusion ListBox with native grouping
|
|
13
|
+
* - Searchable via filter
|
|
14
|
+
* - Click outside to close
|
|
15
|
+
* - Escape key to close
|
|
16
|
+
* - Component selection triggers onSelect callback
|
|
17
|
+
*
|
|
18
|
+
* @param props - Component props
|
|
19
|
+
* @returns Popup component with Syncfusion ListBox
|
|
20
|
+
*/
|
|
21
|
+
const ListBoxPane = ({ isOpen, onClose, onSelect, paletteConfig, mode }) => {
|
|
22
|
+
const popupRef = useRef(null);
|
|
23
|
+
const listboxRef = useRef(null);
|
|
24
|
+
const { l10n } = useLocalization();
|
|
25
|
+
const palette = useMemo(() => {
|
|
26
|
+
const merged = { ...defaultPalette };
|
|
27
|
+
if (paletteConfig) {
|
|
28
|
+
Object.entries(paletteConfig).forEach(([category, components]) => {
|
|
29
|
+
if (components) {
|
|
30
|
+
merged[category] = components;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// Resolve labels from l10n and flatten with group info
|
|
35
|
+
const items = [];
|
|
36
|
+
Object.entries(merged).forEach(([category, components]) => {
|
|
37
|
+
(components || []).forEach((comp) => {
|
|
38
|
+
// Filter out advanced components in Simple mode
|
|
39
|
+
if (mode === 'Simple' && simpleModeHiddenTypes.has(comp.type)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
items.push({
|
|
43
|
+
...comp,
|
|
44
|
+
label: comp.label || l10n.getConstant(comp.type) || comp.type,
|
|
45
|
+
group: category
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return items;
|
|
50
|
+
}, [paletteConfig, l10n, mode, simpleModeHiddenTypes]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!isOpen)
|
|
53
|
+
return;
|
|
54
|
+
const handleClickOutside = (event) => {
|
|
55
|
+
if (popupRef.current && !popupRef.current.contains(event.target)) {
|
|
56
|
+
onClose();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
// Handle Escape key
|
|
60
|
+
const handleKeyDown = (event) => {
|
|
61
|
+
if (event.key === 'Escape') {
|
|
62
|
+
onClose();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
66
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
67
|
+
return () => {
|
|
68
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
69
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
70
|
+
};
|
|
71
|
+
}, [isOpen, onClose]);
|
|
72
|
+
const handleSelect = useCallback((args) => {
|
|
73
|
+
if (!args.event.isTrusted) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const dataType = args.elements[0].getAttribute('data-value');
|
|
77
|
+
const selectedComponent = palette.find((p) => p.type === dataType);
|
|
78
|
+
if (selectedComponent) {
|
|
79
|
+
onSelect({
|
|
80
|
+
type: selectedComponent.type,
|
|
81
|
+
label: selectedComponent.label
|
|
82
|
+
});
|
|
83
|
+
onClose();
|
|
84
|
+
}
|
|
85
|
+
}, [palette, onSelect, onClose]);
|
|
86
|
+
if (!isOpen)
|
|
87
|
+
return null;
|
|
88
|
+
return (_jsx("div", { className: "listbox-pane-wrapper", "aria-modal": "true", tabIndex: -1, role: 'dialog', "aria-label": "Component Selection", onClick: (e) => {
|
|
89
|
+
if (e.target === e.currentTarget) {
|
|
90
|
+
onClose();
|
|
91
|
+
}
|
|
92
|
+
}, children: _jsx("div", { className: "listbox-pane-popup", ref: popupRef, "aria-label": "Available Components", children: _jsx(ListBoxComponent, { ref: listboxRef, dataSource: palette, tabIndex: 0, fields: { groupBy: 'group', text: 'label', value: 'type' }, change: handleSelect, allowFiltering: true, filterBarPlaceholder: "Search", height: "400px", "aria-label": "Available Components", itemTemplate: (props) => (_jsxs(ButtonComponent, { cssClass: 'e-flat', style: { display: 'flex', justifyContent: 'flex-start', padding: 0, fontWeight: 500, backgroundColor: 'transparent', border: 'none' }, "data-type": props.type, className: "listbox-item-with-icon", "aria-label": props.label, children: [_jsx("div", { "aria-hidden": true, className: "listbox-item-icon", children: getIconComponent(props.type) }), _jsx("span", { className: "listbox-item-label", children: props.label })] })) }) }) }));
|
|
93
|
+
};
|
|
94
|
+
export default ListBoxPane;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type FormSchema, type FormComponent } from '../../types/FormSchema';
|
|
2
|
+
import { type FieldDragStartedEventArgs, type FieldDraggingOverEventArgs, type FieldDroppedEventArgs } from '../../FormBuilder';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
interface MainSectionProps {
|
|
5
|
+
formSchema: FormSchema;
|
|
6
|
+
enablePreview?: boolean;
|
|
7
|
+
activeFieldId?: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
mode?: 'Simple' | 'Developer';
|
|
10
|
+
paletteConfig?: any;
|
|
11
|
+
onDrop: (component: FormComponent, dropTargetIndex?: number | null, dropPosition?: 'before' | 'after' | null) => void;
|
|
12
|
+
onNestedDrop: (parentId: string, component: FormComponent, cellInfo?: {
|
|
13
|
+
row: number;
|
|
14
|
+
col: number;
|
|
15
|
+
tabIndex?: number;
|
|
16
|
+
}, parentPath?: string, dropTargetIndex?: number | null, dropPosition?: 'before' | 'after' | null) => void;
|
|
17
|
+
onReorder: (newComponents: FormComponent[]) => void;
|
|
18
|
+
onNestedReorder: (parentId: string, parentPath: string, newChildren: FormComponent[], cellInfo?: {
|
|
19
|
+
row?: number;
|
|
20
|
+
col?: number;
|
|
21
|
+
tabIndex?: number;
|
|
22
|
+
}) => void;
|
|
23
|
+
onDelete: (fieldId: string, isReordering?: boolean) => void;
|
|
24
|
+
onEdit: (fieldId: string) => void;
|
|
25
|
+
onCopy: (field: FormComponent) => void;
|
|
26
|
+
onPaste: (targetFieldId: string) => void;
|
|
27
|
+
onDeleteAction: (fieldId: string) => void;
|
|
28
|
+
onSwitchWidget?: (fieldId: string, newWidgetTypePascal: string) => void;
|
|
29
|
+
hasClipboardData?: boolean;
|
|
30
|
+
onFieldDragStarted?: (args: FieldDragStartedEventArgs) => void;
|
|
31
|
+
onFieldDraggingOver?: (args: FieldDraggingOverEventArgs) => void;
|
|
32
|
+
onFieldDropped?: (args: FieldDroppedEventArgs) => void;
|
|
33
|
+
}
|
|
34
|
+
export declare const isDefaultFormSchema: (schema: FormSchema) => boolean;
|
|
35
|
+
export interface MainSectionRef {
|
|
36
|
+
getGridRefs: () => Record<string, React.RefObject<any>>;
|
|
37
|
+
}
|
|
38
|
+
declare const MainSection: React.ForwardRefExoticComponent<MainSectionProps & React.RefAttributes<MainSectionRef>>;
|
|
39
|
+
export default MainSection;
|