@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.
Files changed (145) hide show
  1. package/Common/CheckboxGroup.d.ts +13 -0
  2. package/Common/CheckboxGroup.js +50 -0
  3. package/Common/ConditionalRuleEngine.d.ts +296 -0
  4. package/Common/ConditionalRuleEngine.js +566 -0
  5. package/Common/CustomTabComponent.d.ts +17 -0
  6. package/Common/CustomTabComponent.js +30 -0
  7. package/Common/CustomValidationEngine.d.ts +58 -0
  8. package/Common/CustomValidationEngine.js +146 -0
  9. package/Common/DataGridComponent.d.ts +20 -0
  10. package/Common/DataGridComponent.js +273 -0
  11. package/Common/FormValidator.d.ts +485 -0
  12. package/Common/FormValidator.js +575 -0
  13. package/Common/MinMaxRuleEngine.d.ts +105 -0
  14. package/Common/MinMaxRuleEngine.js +365 -0
  15. package/Common/StaticHtmlRenderer.d.ts +14 -0
  16. package/Common/StaticHtmlRenderer.js +24 -0
  17. package/Common/expressions/DependencyTracker.d.ts +30 -0
  18. package/Common/expressions/DependencyTracker.js +128 -0
  19. package/Common/expressions/ExpressionEngine.d.ts +95 -0
  20. package/Common/expressions/ExpressionEngine.js +506 -0
  21. package/Common/expressions/ExpressionParser.d.ts +41 -0
  22. package/Common/expressions/ExpressionParser.js +269 -0
  23. package/Common/expressions/useGridExpressions.d.ts +12 -0
  24. package/Common/expressions/useGridExpressions.js +71 -0
  25. package/Common/globalFormId.d.ts +26 -0
  26. package/Common/globalFormId.js +45 -0
  27. package/Common/json-converter.d.ts +444 -0
  28. package/Common/json-converter.js +1184 -0
  29. package/Common/parser.d.ts +98 -0
  30. package/Common/parser.js +753 -0
  31. package/Common/utils.d.ts +130 -0
  32. package/Common/utils.js +574 -0
  33. package/README.md +53 -0
  34. package/index.d.ts +6 -0
  35. package/index.js +6 -0
  36. package/license +13 -0
  37. package/package.json +54 -0
  38. package/src/FormBuilder.d.ts +211 -0
  39. package/src/FormBuilder.js +1299 -0
  40. package/src/components/Database/ConnectionForm.d.ts +8 -0
  41. package/src/components/Database/ConnectionForm.js +28 -0
  42. package/src/components/TopBar.d.ts +10 -0
  43. package/src/components/TopBar.js +7 -0
  44. package/src/components/icons/ComponentIcons.d.ts +44 -0
  45. package/src/components/icons/ComponentIcons.js +40 -0
  46. package/src/components/layout/FormFieldWrapper.d.ts +41 -0
  47. package/src/components/layout/FormFieldWrapper.js +112 -0
  48. package/src/components/layout/LeftPane.d.ts +23 -0
  49. package/src/components/layout/LeftPane.js +310 -0
  50. package/src/components/layout/ListBoxPane.d.ts +24 -0
  51. package/src/components/layout/ListBoxPane.js +94 -0
  52. package/src/components/layout/MainSection.d.ts +39 -0
  53. package/src/components/layout/MainSection.js +1766 -0
  54. package/src/components/layout/Preview.d.ts +12 -0
  55. package/src/components/layout/Preview.js +337 -0
  56. package/src/components/layout/RightPane.d.ts +22 -0
  57. package/src/components/layout/RightPane.js +370 -0
  58. package/src/components/theme/ThemeSwitcher.d.ts +14 -0
  59. package/src/components/theme/ThemeSwitcher.js +24 -0
  60. package/src/components/theme/theme-constant.d.ts +37 -0
  61. package/src/components/theme/theme-constant.js +23 -0
  62. package/src/config/component-properties.json +4929 -0
  63. package/src/config/form-setting.json +56 -0
  64. package/src/config/locale.d.ts +449 -0
  65. package/src/config/locale.js +449 -0
  66. package/src/context/FormSchemaContext.d.ts +32 -0
  67. package/src/context/FormSchemaContext.js +155 -0
  68. package/src/context/LocalizationContext.d.ts +27 -0
  69. package/src/context/LocalizationContext.js +43 -0
  70. package/src/context/ThemeContext.d.ts +23 -0
  71. package/src/context/ThemeContext.js +161 -0
  72. package/src/context/contexts.d.ts +6 -0
  73. package/src/context/contexts.js +6 -0
  74. package/src/context/defaultFormSchema.d.ts +6 -0
  75. package/src/context/defaultFormSchema.js +20 -0
  76. package/src/defaultPalette.d.ts +9 -0
  77. package/src/defaultPalette.js +45 -0
  78. package/src/defaultTemplate.d.ts +3 -0
  79. package/src/defaultTemplate.js +185 -0
  80. package/src/import/schemaImportUtils.d.ts +24 -0
  81. package/src/import/schemaImportUtils.js +121 -0
  82. package/src/index.d.ts +16 -0
  83. package/src/index.js +6 -0
  84. package/src/types/ActionButtons.d.ts +44 -0
  85. package/src/types/ActionButtons.js +27 -0
  86. package/src/types/ComponentProperties.d.ts +33 -0
  87. package/src/types/ComponentProperties.js +2 -0
  88. package/src/types/FormSchema.d.ts +585 -0
  89. package/src/types/FormSchema.js +147 -0
  90. package/src/types/connection.d.ts +19 -0
  91. package/src/types/connection.js +9 -0
  92. package/src/utils/AttributesEditor.d.ts +13 -0
  93. package/src/utils/AttributesEditor.js +117 -0
  94. package/src/utils/ButtonGroupEditor.d.ts +15 -0
  95. package/src/utils/ButtonGroupEditor.js +141 -0
  96. package/src/utils/ChoiceBasedFieldEditor.d.ts +16 -0
  97. package/src/utils/ChoiceBasedFieldEditor.js +87 -0
  98. package/src/utils/ClipboardHelper.d.ts +26 -0
  99. package/src/utils/ClipboardHelper.js +83 -0
  100. package/src/utils/ColumnBuilder.d.ts +11 -0
  101. package/src/utils/ColumnBuilder.js +153 -0
  102. package/src/utils/ColumnSpanEditor.d.ts +8 -0
  103. package/src/utils/ColumnSpanEditor.js +98 -0
  104. package/src/utils/ConditionDataEditor.d.ts +29 -0
  105. package/src/utils/ConditionDataEditor.js +177 -0
  106. package/src/utils/ConditionalEditorUtils.d.ts +27 -0
  107. package/src/utils/ConditionalEditorUtils.js +310 -0
  108. package/src/utils/ConditionsEditor.d.ts +20 -0
  109. package/src/utils/ConditionsEditor.js +571 -0
  110. package/src/utils/CustomAccordion.d.ts +13 -0
  111. package/src/utils/CustomAccordion.js +38 -0
  112. package/src/utils/CustomValidationEditor.d.ts +11 -0
  113. package/src/utils/CustomValidationEditor.js +208 -0
  114. package/src/utils/DataSourceCollector.d.ts +3 -0
  115. package/src/utils/DataSourceCollector.js +252 -0
  116. package/src/utils/ExpressionEditor.d.ts +13 -0
  117. package/src/utils/ExpressionEditor.js +99 -0
  118. package/src/utils/FormSettingsPanel.d.ts +9 -0
  119. package/src/utils/FormSettingsPanel.js +60 -0
  120. package/src/utils/LookupConfig.d.ts +11 -0
  121. package/src/utils/LookupConfig.js +159 -0
  122. package/src/utils/MinMaxValueEditor.d.ts +25 -0
  123. package/src/utils/MinMaxValueEditor.js +188 -0
  124. package/src/utils/OptionsEditor.d.ts +8 -0
  125. package/src/utils/OptionsEditor.js +79 -0
  126. package/src/utils/PropertyRenderer.d.ts +22 -0
  127. package/src/utils/PropertyRenderer.js +210 -0
  128. package/src/utils/ValueRenderer.d.ts +2 -0
  129. package/src/utils/ValueRenderer.js +91 -0
  130. package/src/utils/ej2DbAdaptor.d.ts +6 -0
  131. package/src/utils/ej2DbAdaptor.js +38 -0
  132. package/src/utils/mappingHeuristics.d.ts +10 -0
  133. package/src/utils/mappingHeuristics.js +33 -0
  134. package/src/utils/renderComponent.d.ts +18 -0
  135. package/src/utils/renderComponent.js +300 -0
  136. package/styles/tailwind3.css +67 -0
  137. package/styles/themes/action-buttons.css +110 -0
  138. package/styles/themes/datagrid.css +380 -0
  139. package/styles/themes/drag-reorder.css +248 -0
  140. package/styles/themes/import-dialog.css +292 -0
  141. package/styles/themes/index.css +658 -0
  142. package/styles/themes/left-pane.css +253 -0
  143. package/styles/themes/options-editor.css +63 -0
  144. package/styles/themes/preview.css +702 -0
  145. package/styles/themes/property-dialog.css +371 -0
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect } from 'react';
3
+ import { PropertyGroupRenderer } from './PropertyRenderer';
4
+ import { useLocalization } from '../context/LocalizationContext';
5
+ const filterFormSettings = (settings, defaults) => {
6
+ const filtered = {};
7
+ Object.keys(settings).forEach(key => {
8
+ // Only include properties that differ from defaults, except for 'name' which is always included
9
+ if (key === 'name' || settings[key] !== defaults[key]) {
10
+ filtered[key] = settings[key];
11
+ }
12
+ });
13
+ return filtered;
14
+ };
15
+ // Get default values from form-setting.json
16
+ const getDefaultFormSettings = (config) => {
17
+ const defaults = {};
18
+ config?.groups?.forEach((group) => {
19
+ group.properties?.forEach((prop) => {
20
+ if (prop.key && prop.default !== undefined) {
21
+ defaults[prop.key] = prop.default;
22
+ }
23
+ });
24
+ });
25
+ return defaults;
26
+ };
27
+ export const FormSettingsPanel = ({ settings, formSettingsConfig, onSave }) => {
28
+ // Initialize with current settings or defaults
29
+ const defaultSettings = getDefaultFormSettings(formSettingsConfig);
30
+ let settingsRef = {
31
+ ...defaultSettings,
32
+ ...settings,
33
+ };
34
+ const { l10n } = useLocalization();
35
+ // Update ref when settings prop changes
36
+ useEffect(() => {
37
+ settingsRef = {
38
+ ...defaultSettings,
39
+ ...settings
40
+ };
41
+ }, [settings, defaultSettings, formSettingsConfig]);
42
+ const handlePropertyChange = (key, value) => {
43
+ const newSettings = {
44
+ ...settingsRef,
45
+ [key]: value
46
+ };
47
+ settingsRef = newSettings;
48
+ // Only save the filtered settings (properties that differ from defaults)
49
+ const filteredSettings = filterFormSettings(newSettings, defaultSettings);
50
+ onSave(filteredSettings, key);
51
+ };
52
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sidebar-header", children: _jsx("h3", { children: l10n.getConstant('formsettings') }) }), _jsx("div", { className: "form-settings property-content", role: "region", "aria-labelledby": "form-settings-heading", children: _jsx("div", { className: "form-properties-section fb-scrollbar", style: { overflowY: "auto" }, "aria-label": l10n.getConstant('formsettings'), children: formSettingsConfig?.groups?.map((group, index) => {
53
+ const isLast = index === (formSettingsConfig.groups || []).length - 1;
54
+ return (_jsxs("div", { role: "group", "aria-labelledby": `settings-group-${index}`, children: [_jsx("h4", { id: `settings-group-${index}`, style: { margin: "12px 12px 0 12px", fontWeight: 600 }, children: l10n.getConstant(group.name.toLowerCase()) }), _jsx(PropertyGroupRenderer, { groupName: group.name, properties: group.properties, initialValues: settingsRef, onChange: handlePropertyChange }), isLast ? null : (_jsx("div", { style: {
55
+ margin: '0',
56
+ padding: 0,
57
+ borderBottom: '1px solid rgb(var(--sf-color-line-color))'
58
+ }, "aria-hidden": "true" }))] }, `group-${index}`));
59
+ }) }) })] }));
60
+ };
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { type LookupConfig as LookupConfigType } from '../types/FormSchema';
3
+ interface LookupConfigProps {
4
+ value: LookupConfigType & {
5
+ selectionType?: string;
6
+ displayFormat?: string;
7
+ };
8
+ onChange: (config: any) => void;
9
+ }
10
+ export declare const LookupConfig: React.FC<LookupConfigProps>;
11
+ export default LookupConfig;
@@ -0,0 +1,159 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useCallback, useMemo } from 'react';
3
+ import { isNullOrUndefined } from '@syncfusion/ej2-base';
4
+ import { DropDownListComponent } from '@syncfusion/ej2-react-dropdowns';
5
+ import { useLocalization } from '../context/LocalizationContext';
6
+ export const LookupConfig = ({ value, onChange }) => {
7
+ const { l10n } = useLocalization();
8
+ const [formNames, setFormNames] = useState([]);
9
+ const [formResponseObjects, setFormResponseObjects] = useState([]);
10
+ const [formFields, setFormFields] = useState([]);
11
+ const [selectedFormId, setSelectedFormId] = useState('');
12
+ const [selectedFormName, setSelectedFormName] = useState(value?.formName || '');
13
+ const [selectedField, setSelectedField] = useState(value?.field || '');
14
+ const [selectedSelectionType, setSelectedSelectionType] = useState(value?.selectionType || 'Single');
15
+ const [selectedDisplayFormat, setSelectedDisplayFormat] = useState(value?.displayFormat || 'Dropdown');
16
+ const [isLoadingForms, setIsLoadingForms] = useState(true);
17
+ const [isLoadingFields, setIsLoadingFields] = useState(false);
18
+ /**
19
+ * Fetch all form names from backend that have submissions
20
+ */
21
+ useEffect(() => {
22
+ const fetchFormNames = async () => {
23
+ try {
24
+ const response = await fetch(/*formNameUrl*/ '');
25
+ if (!response.ok) {
26
+ throw new Error(`Network response was not ok: ${response.status} ${response.statusText}`);
27
+ }
28
+ const data = await response.json();
29
+ const names = Array.isArray(data) ? data.map((item) => item.name).filter(Boolean) : [];
30
+ const objects = Array.isArray(data)
31
+ ? data.map((item) => ({ id: item.id, name: item.name })).filter((o) => o && o.name)
32
+ : [];
33
+ setFormNames(names);
34
+ setFormResponseObjects(objects);
35
+ }
36
+ catch (error) {
37
+ console.error('Error fetching form names:', error);
38
+ setFormNames([]);
39
+ setFormResponseObjects([]);
40
+ }
41
+ finally {
42
+ setIsLoadingForms(false);
43
+ }
44
+ };
45
+ fetchFormNames();
46
+ }, []);
47
+ /**
48
+ * Fetch fields from selected form's submissions via backend API
49
+ */
50
+ useEffect(() => {
51
+ if (!selectedFormName) {
52
+ setFormFields([]);
53
+ return;
54
+ }
55
+ setIsLoadingFields(true);
56
+ const fetchFields = async () => {
57
+ try {
58
+ if (!isNullOrUndefined(selectedFormId) && selectedFormId !== '') {
59
+ setIsLoadingFields(true);
60
+ const response = await fetch(/*getRecordsUrl*/ '' + `${selectedFormId}`);
61
+ const submissions = await response.json();
62
+ if (submissions.length > 0) {
63
+ const fields = Object.keys(submissions[0]).filter(key => !['id', 'timestamp'].includes(key));
64
+ setFormFields(fields);
65
+ }
66
+ setIsLoadingFields(false);
67
+ }
68
+ }
69
+ catch (error) {
70
+ console.error('Error fetching form fields:', error);
71
+ setFormFields([]);
72
+ }
73
+ };
74
+ fetchFields();
75
+ }, [selectedFormName]);
76
+ /**
77
+ * Get filtered displayFormat options based on selectionType
78
+ */
79
+ const displayFormatOptions = useMemo(() => {
80
+ const allOptions = ['Dropdown', 'Radio', 'MultiSelect', 'Checkbox'];
81
+ const normalizedType = selectedSelectionType?.toLowerCase() || 'single';
82
+ if (normalizedType === 'single') {
83
+ return allOptions.filter(opt => ['dropdown', 'radio'].includes(opt.toLowerCase()));
84
+ }
85
+ else if (normalizedType === 'multiple') {
86
+ return allOptions.filter(opt => ['multiselect', 'checkbox'].includes(opt.toLowerCase()));
87
+ }
88
+ return allOptions;
89
+ }, [selectedSelectionType]);
90
+ /**
91
+ * Reset displayFormat if it's not valid for the new selectionType
92
+ */
93
+ useEffect(() => {
94
+ if (!displayFormatOptions.includes(selectedDisplayFormat)) {
95
+ setSelectedDisplayFormat(displayFormatOptions[0] || 'Dropdown');
96
+ }
97
+ }, [displayFormatOptions, selectedDisplayFormat]);
98
+ const handleFormNameChange = useCallback((args) => {
99
+ const newId = args.value;
100
+ const matched = formResponseObjects.find(o => o.name === newId);
101
+ const newFormName = matched ? matched.name : '';
102
+ const newFormId = matched ? matched.id : '';
103
+ setSelectedFormName(newFormName);
104
+ setSelectedFormId(newFormId);
105
+ setSelectedField('');
106
+ onChange({
107
+ formName: newFormName,
108
+ field: '',
109
+ selectionType: selectedSelectionType,
110
+ displayFormat: selectedDisplayFormat
111
+ });
112
+ }, [onChange, selectedSelectionType, selectedDisplayFormat, formResponseObjects]);
113
+ const handleFieldChange = useCallback((args) => {
114
+ const newField = args.value;
115
+ setSelectedField(newField);
116
+ onChange({
117
+ formName: selectedFormName,
118
+ field: newField,
119
+ selectionType: selectedSelectionType,
120
+ displayFormat: selectedDisplayFormat
121
+ });
122
+ }, [onChange, selectedFormName, selectedSelectionType, selectedDisplayFormat]);
123
+ const handleSelectionTypeChange = useCallback((args) => {
124
+ const newSelectionType = args.value;
125
+ setSelectedSelectionType(newSelectionType);
126
+ // Calculate the correct displayFormat for this selectionType immediately
127
+ const normalizedType = newSelectionType?.toLowerCase() || 'single';
128
+ let validDisplayFormat;
129
+ if (normalizedType === 'single') {
130
+ validDisplayFormat = 'Dropdown'; // Default for single
131
+ }
132
+ else if (normalizedType === 'multiple') {
133
+ validDisplayFormat = 'MultiSelect'; // Default for multiple
134
+ }
135
+ else {
136
+ validDisplayFormat = 'Dropdown';
137
+ }
138
+ setSelectedDisplayFormat(validDisplayFormat);
139
+ // Send updated values with correct displayFormat
140
+ onChange({
141
+ formName: selectedFormName,
142
+ field: selectedField,
143
+ selectionType: newSelectionType,
144
+ displayFormat: validDisplayFormat
145
+ });
146
+ }, [onChange, selectedFormName, selectedField]);
147
+ const handleDisplayFormatChange = useCallback((args) => {
148
+ const newDisplayFormat = args.value;
149
+ setSelectedDisplayFormat(newDisplayFormat);
150
+ onChange({
151
+ formName: selectedFormName,
152
+ field: selectedField,
153
+ selectionType: selectedSelectionType,
154
+ displayFormat: newDisplayFormat
155
+ });
156
+ }, [onChange, selectedFormName, selectedField, selectedSelectionType]);
157
+ return (_jsxs("div", { className: "lookup-config-container", style: { display: 'flex', flexDirection: 'column', gap: '0.5rem' }, children: [_jsxs("div", { className: "lookup-config-group", style: { marginBottom: '0.5rem' }, children: [_jsx("label", { className: "property-label", style: { marginBottom: '0.5rem' }, children: l10n.getConstant('selectionType') || 'Selection Type' }), _jsx(DropDownListComponent, { dataSource: ['Single', 'Multiple'], value: selectedSelectionType, change: handleSelectionTypeChange, placeholder: l10n.getConstant('selectSelectionType') || 'Select selection type...', popupHeight: "100px" })] }), _jsxs("div", { className: "lookup-config-group", style: { marginBottom: '0.5rem' }, children: [_jsx("label", { className: "property-label", style: { marginBottom: '0.5rem' }, children: l10n.getConstant('displayFormat') || 'Display Format' }), _jsx(DropDownListComponent, { dataSource: displayFormatOptions, value: selectedDisplayFormat, change: handleDisplayFormatChange, placeholder: l10n.getConstant('selectDisplayFormat') || 'Select display format...', popupHeight: "150px" })] }), _jsx("label", { className: "property-label", children: l10n.getConstant('lookupConfig') }), _jsxs("div", { className: "lookup-config-group", children: [_jsx("label", { style: { display: 'block', marginBottom: '0.5rem', fontWeight: 500 }, children: l10n.getConstant('selectFormName') || 'Select Form Name' }), _jsx(DropDownListComponent, { dataSource: formNames, value: selectedFormName, change: handleFormNameChange, placeholder: l10n.getConstant('selectForm') || 'Select a form...', popupHeight: "200px", allowFiltering: true, enabled: !isLoadingForms })] }), selectedFormName && (_jsxs("div", { className: "lookup-config-group", children: [_jsx("label", { style: { display: 'block', marginBottom: '0.5rem', fontWeight: 500 }, children: l10n.getConstant('selectField') || 'Select Field' }), _jsx(DropDownListComponent, { dataSource: formFields, value: selectedField, change: handleFieldChange, placeholder: l10n.getConstant('selectField') || 'Select field...', popupHeight: "200px", allowFiltering: true, enabled: !isLoadingFields })] }))] }));
158
+ };
159
+ export default LookupConfig;
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ interface MinMaxRangeData {
3
+ enabled: boolean;
4
+ minMode?: 'value' | 'field';
5
+ minValue?: number | string | {
6
+ field: string;
7
+ };
8
+ maxMode?: 'value' | 'field';
9
+ maxValue?: number | string | {
10
+ field: string;
11
+ };
12
+ }
13
+ interface MinMaxValueEditorProps {
14
+ label: string;
15
+ initialValue: MinMaxRangeData[];
16
+ mode: 'number' | 'date' | 'time';
17
+ onChange: (value: MinMaxRangeData[]) => void;
18
+ locale?: string;
19
+ description?: string;
20
+ disabled?: boolean;
21
+ currentFieldId?: string;
22
+ builderMode?: 'Simple' | 'Developer';
23
+ }
24
+ export declare const MinMaxValueEditor: React.FC<MinMaxValueEditorProps>;
25
+ export default MinMaxValueEditor;
@@ -0,0 +1,188 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useMemo } from 'react';
3
+ import { NumericTextBoxComponent } from '@syncfusion/ej2-react-inputs';
4
+ import { DropDownListComponent } from '@syncfusion/ej2-react-dropdowns';
5
+ import { ButtonComponent, CheckBoxComponent } from '@syncfusion/ej2-react-buttons';
6
+ import { DatePickerComponent, TimePickerComponent } from '@syncfusion/ej2-react-calendars';
7
+ import { useLocalization } from '../context/LocalizationContext';
8
+ import { useFormSchema } from '../context/FormSchemaContext';
9
+ import { Internationalization } from '@syncfusion/ej2-base';
10
+ export const MinMaxValueEditor = ({ label, initialValue = [], mode, onChange, disabled = false, currentFieldId, builderMode, }) => {
11
+ const { l10n, locale } = useLocalization();
12
+ const { formSchema } = useFormSchema();
13
+ let intl = new Internationalization();
14
+ const [state, setState] = useState(() => {
15
+ // Detect if initialValue has field-based ranges
16
+ const hasFieldData = Array.isArray(initialValue) && initialValue.some(r => (typeof r.minValue === 'object' && r.minValue?.field) ||
17
+ (typeof r.maxValue === 'object' && r.maxValue?.field));
18
+ return {
19
+ useFieldMode: hasFieldData,
20
+ ranges: Array.isArray(initialValue) && initialValue.length > 0 ? initialValue : [{ enabled: false }]
21
+ };
22
+ });
23
+ const availableFields = useMemo(() => {
24
+ if (!formSchema?.components)
25
+ return [];
26
+ const fields = [];
27
+ const traverse = (components) => {
28
+ components.forEach(comp => {
29
+ if (comp.name === currentFieldId)
30
+ return;
31
+ const isNumber = ['number'].includes(comp.type);
32
+ const isDate = ['date'].includes(comp.type);
33
+ const isTime = ['time'].includes(comp.type);
34
+ if ((isNumber && mode === 'number') || (isDate && mode === 'date') || (isTime && mode === 'time')) {
35
+ fields.push({
36
+ id: comp.id,
37
+ name: comp.label || comp.name || comp.id,
38
+ type: isNumber ? 'number' : isTime ? 'time' : 'date'
39
+ });
40
+ }
41
+ if (comp.children?.length)
42
+ traverse(comp.children);
43
+ if (comp.tableCells) {
44
+ comp.tableCells.forEach(row => {
45
+ row.forEach(cell => {
46
+ traverse(cell);
47
+ });
48
+ });
49
+ }
50
+ if (comp.tabItems) {
51
+ comp.tabItems.forEach(tab => {
52
+ if (tab.content)
53
+ traverse(tab.content);
54
+ });
55
+ }
56
+ });
57
+ };
58
+ traverse(formSchema.components);
59
+ return fields.sort((a, b) => a.name.localeCompare(b.name));
60
+ }, [formSchema, mode, currentFieldId]);
61
+ const dropdownDataSource = availableFields.map(f => ({
62
+ text: f.name,
63
+ value: f.id
64
+ }));
65
+ const handleToggle = (checked) => {
66
+ const newState = {
67
+ useFieldMode: checked,
68
+ ranges: checked ? [{ enabled: true }] : [{ enabled: false }]
69
+ };
70
+ setState(newState);
71
+ onChange(newState.ranges);
72
+ };
73
+ const handleMinChange = (newValue) => {
74
+ const updated = [...state.ranges];
75
+ updated[0] = { ...updated[0], minValue: newValue };
76
+ setState({ ...state, ranges: updated });
77
+ onChange(updated);
78
+ };
79
+ const handleMaxChange = (newValue) => {
80
+ const updated = [...state.ranges];
81
+ updated[0] = { ...updated[0], maxValue: newValue };
82
+ setState({ ...state, ranges: updated });
83
+ onChange(updated);
84
+ };
85
+ const handleMinFieldChange = (index, fieldId) => {
86
+ const updated = [...state.ranges];
87
+ updated[index] = { ...updated[index], minValue: { field: fieldId } };
88
+ setState({ ...state, ranges: updated });
89
+ onChange(updated);
90
+ };
91
+ const handleMaxFieldChange = (index, fieldId) => {
92
+ const updated = [...state.ranges];
93
+ updated[index] = { ...updated[index], maxValue: { field: fieldId } };
94
+ setState({ ...state, ranges: updated });
95
+ onChange(updated);
96
+ };
97
+ const handleDelete = (index) => {
98
+ const updated = state.ranges.filter((_, i) => i !== index);
99
+ setState({ ...state, ranges: updated });
100
+ onChange(updated);
101
+ };
102
+ const handleAddRange = () => {
103
+ const updated = [...state.ranges, { enabled: true }];
104
+ setState({ ...state, ranges: updated });
105
+ onChange(updated);
106
+ };
107
+ const getMinValue = () => {
108
+ const range = state.ranges[0];
109
+ if (typeof range?.minValue === 'object')
110
+ return '';
111
+ return range?.minValue ?? '';
112
+ };
113
+ const getMaxValue = () => {
114
+ const range = state.ranges[0];
115
+ if (typeof range?.maxValue === 'object')
116
+ return '';
117
+ return range?.maxValue ?? '';
118
+ };
119
+ const getMinFieldId = (index) => {
120
+ const range = state.ranges[index];
121
+ if (typeof range?.minValue === 'object' && range?.minValue?.field) {
122
+ return range.minValue.field;
123
+ }
124
+ return '';
125
+ };
126
+ const getMaxFieldId = (index) => {
127
+ const range = state.ranges[index];
128
+ if (typeof range?.maxValue === 'object' && range?.maxValue?.field) {
129
+ return range.maxValue.field;
130
+ }
131
+ return '';
132
+ };
133
+ return (_jsxs("div", { className: `minmax-editor`, role: "group", "aria-label": label, children: [builderMode === 'Developer' &&
134
+ _jsx("div", { className: "minmax-toggle-section", children: _jsx(CheckBoxComponent, { id: `minmax-toggle-${label?.replace(/\s+/g, '_')}`, label: l10n.getConstant('useFieldValue'), checked: state.useFieldMode, cssClass: "e-small", locale: locale, change: (e) => handleToggle(e.checked), disabled: disabled || availableFields.length === 0 }) }), !state.useFieldMode && (_jsxs("div", { className: "minmax-inputs-row", children: [_jsxs("div", { className: "minmax-input-group", children: [_jsx("label", { className: "minmax-input-label", children: l10n.getConstant('minValue') }), mode === 'number' && (_jsx(NumericTextBoxComponent, { value: getMinValue(), width: '118px', change: (e) => handleMinChange(e.value), disabled: disabled, locale: locale, format: 'n', cssClass: "minmax-numeric", htmlAttributes: {
135
+ 'aria-label': `min-value-label-${label?.replace(/\s+/g, '_')}`
136
+ } })), " ", mode === 'date' && (_jsx(DatePickerComponent, { value: undefined, width: '118px', format: "MM/dd/yyyy", locale: locale, change: (e) => {
137
+ if (e.value) {
138
+ let dFormatter = intl.getDateFormat({
139
+ format: 'MM/dd/yyyy',
140
+ type: 'date'
141
+ });
142
+ let value = dFormatter(e.value);
143
+ handleMinChange(value);
144
+ }
145
+ }, disabled: disabled, cssClass: "minmax-date", htmlAttributes: {
146
+ 'aria-label': `min-value-label-${label?.replace(/\s+/g, '_')}`
147
+ } })), mode === 'time' && (_jsx(TimePickerComponent, { value: undefined, width: '118px', format: "hh:mm a", locale: locale, change: (e) => {
148
+ if (e.value) {
149
+ let dFormatter = intl.getDateFormat({
150
+ format: 'hh:mm a',
151
+ type: 'time'
152
+ });
153
+ let value = dFormatter(e.value);
154
+ handleMinChange(value);
155
+ }
156
+ }, disabled: disabled, cssClass: "minmax-date", htmlAttributes: {
157
+ 'aria-label': `min-value-label-${label?.replace(/\s+/g, '_')}`
158
+ } }))] }), _jsxs("div", { className: "minmax-input-group", children: [_jsx("label", { className: "minmax-input-label", children: l10n.getConstant('maxValue') }), mode === 'number' && (_jsx(NumericTextBoxComponent, { value: getMaxValue(), width: '118px', change: (e) => handleMaxChange(e.value), format: 'n', locale: locale, disabled: disabled, cssClass: "minmax-numeric", htmlAttributes: {
159
+ 'aria-label': `max-value-label-${label?.replace(/\s+/g, '_')}`
160
+ } })), " ", mode === 'date' && (_jsx(DatePickerComponent, { value: undefined, format: "MM/dd/yyyy", width: '118px', locale: locale, change: (e) => {
161
+ if (e.value) {
162
+ let dFormatter = intl.getDateFormat({
163
+ format: 'MM/dd/yyyy',
164
+ type: 'date'
165
+ });
166
+ let value = dFormatter(e.value);
167
+ handleMaxChange(value);
168
+ }
169
+ }, disabled: disabled, cssClass: "minmax-date", htmlAttributes: {
170
+ 'aria-label': `max-value-label-${label?.replace(/\s+/g, '_')}`
171
+ } })), mode === 'time' && (_jsx(TimePickerComponent, { value: undefined, width: '118px', locale: locale, format: "hh:mm a", change: (e) => {
172
+ if (e.value) {
173
+ let dFormatter = intl.getDateFormat({
174
+ format: 'hh:mm a',
175
+ type: 'time'
176
+ });
177
+ let value = dFormatter(e.value);
178
+ handleMaxChange(value);
179
+ }
180
+ }, disabled: disabled, cssClass: "minmax-date", htmlAttributes: {
181
+ 'aria-label': `max-value-label-${label?.replace(/\s+/g, '_')}`
182
+ } }))] })] })), state.useFieldMode && (_jsxs(_Fragment, { children: [state.ranges.map((_range, index) => (_jsxs("div", { className: "minmax-inputs-row", style: { marginBottom: "0.75rem" }, children: [_jsxs("div", { className: "minmax-input-group", children: [_jsx("label", { className: "minmax-input-label", children: l10n.getConstant('minValue') }), _jsx(DropDownListComponent, { dataSource: dropdownDataSource, fields: { text: 'text', value: 'value' }, width: '100px', popupWidth: '200px', locale: locale, value: getMinFieldId(index), change: (e) => handleMinFieldChange(index, e.value), disabled: disabled, cssClass: "minmax-dropdown", htmlAttributes: {
183
+ 'aria-label': `min-field-label-${index}`
184
+ } })] }), _jsxs("div", { className: "minmax-input-group", children: [_jsx("label", { className: "minmax-input-label", children: l10n.getConstant('maxValue') }), _jsx(DropDownListComponent, { dataSource: dropdownDataSource, fields: { text: 'text', value: 'value' }, width: '100px', locale: locale, popupWidth: '200px', value: getMaxFieldId(index), change: (e) => handleMaxFieldChange(index, e.value), disabled: disabled, cssClass: "minmax-dropdown", htmlAttributes: {
185
+ 'aria-label': `max-field-label-${index}`
186
+ } })] }), _jsx("div", { className: "minmax-delete-group", children: _jsx(ButtonComponent, { onClick: () => handleDelete(index), disabled: disabled, iconCss: "e-icons e-trash", cssClass: 'e-outlined', title: "Delete range", "aria-label": `${l10n.getConstant('delete')} ${l10n.getConstant('addRange')} ${index + 1}` }) })] }, index))), _jsx("div", { className: "minmax-add-range-section", children: _jsx(ButtonComponent, { className: "minmax-add-range-btn", onClick: handleAddRange, disabled: disabled, "aria-label": l10n.getConstant('addRange'), children: l10n.getConstant('addRange') }) })] }))] }));
187
+ };
188
+ export default MinMaxValueEditor;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ interface OptionsEditorProps {
3
+ options: string[];
4
+ onChange: (options: string[]) => void;
5
+ type: string;
6
+ }
7
+ export declare const OptionsEditor: React.FC<OptionsEditorProps>;
8
+ export {};
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useRef } from 'react';
3
+ import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
4
+ import { GridComponent, ColumnsDirective, ColumnDirective, Inject, Page } from '@syncfusion/ej2-react-grids';
5
+ import { useLocalization } from '../context/LocalizationContext';
6
+ import { floatBlur, floatFocus } from '../components/layout/LeftPane';
7
+ export const OptionsEditor = ({ options, onChange, type }) => {
8
+ const [rows, setRows] = useState([]);
9
+ const gridRef = useRef(null);
10
+ const { l10n } = useLocalization();
11
+ // Initialize rows from options
12
+ useEffect(() => {
13
+ const initialRows = (options).map((option, idx) => ({
14
+ id: idx,
15
+ index: idx + 1,
16
+ label: l10n.getConstant(option) || option
17
+ }));
18
+ setRows(initialRows);
19
+ }, []);
20
+ // Update parent when rows change
21
+ const updateOptions = (newRows) => {
22
+ const updatedOptions = newRows.map(row => row.label);
23
+ onChange(updatedOptions);
24
+ };
25
+ const handleAddRow = () => {
26
+ const newId = rows.length > 0 ? Math.max(...rows.map(r => r.id)) + 1 : 0;
27
+ const newIndex = rows.length + 1;
28
+ const newRow = {
29
+ id: newId,
30
+ index: newIndex,
31
+ label: type === "tabs" ? `${l10n.getConstant('tab')} ${newIndex}` : `${l10n.getConstant('option')} ${newIndex}`
32
+ };
33
+ const newRows = [...rows, newRow];
34
+ setRows(newRows);
35
+ updateOptions(newRows);
36
+ };
37
+ /* istanbul ignore next */
38
+ const handleDeleteRow = (id) => {
39
+ const newRows = rows.filter(row => row.id !== id).map((row, idx) => ({
40
+ ...row,
41
+ index: idx + 1
42
+ }));
43
+ setRows(newRows);
44
+ updateOptions(newRows);
45
+ };
46
+ /* istanbul ignore next */
47
+ const handleLabelChange = (id, newLabel) => {
48
+ if (rows[id].label === newLabel) {
49
+ return;
50
+ }
51
+ const newRows = rows.map(row => row.id === id ? { ...row, label: newLabel } : row);
52
+ setRows(newRows);
53
+ updateOptions(newRows);
54
+ };
55
+ /* istanbul ignore next */
56
+ const actionTemplate = (props) => (_jsx("button", { onClick: () => handleDeleteRow(props.id), onKeyDown: (e) => {
57
+ if (e.code === "Enter") {
58
+ handleDeleteRow(props.id);
59
+ }
60
+ }, "aria-label": 'Delete this option', className: "e-btn e-flat", title: "Delete this option", type: "button", children: _jsx("span", { className: "e-icons e-trash", style: {
61
+ cursor: 'pointer',
62
+ display: 'flex',
63
+ alignItems: 'center',
64
+ justifyContent: 'center',
65
+ flexShrink: 0,
66
+ } }) }));
67
+ /* istanbul ignore next */
68
+ const labelTemplate = (props) => (_jsx("div", { className: "e-input-group", children: _jsx("input", { className: "e-input", type: "text", "aria-label": 'Option Value', defaultValue: props.label, onBlur: (e) => {
69
+ handleLabelChange(props.id, e.target.value);
70
+ floatBlur(e);
71
+ }, onFocus: floatFocus, tabIndex: 0, onKeyDown: (e) => {
72
+ if (e.ctrlKey && e.key === 'a') {
73
+ e.preventDefault();
74
+ e.currentTarget.select();
75
+ }
76
+ }, placeholder: `${type === 'tabs' ? 'Enter tab label' : "Enter option label"}` }) }));
77
+ const height = rows.length >= 3 ? 174 : rows.length == 2 ? 116 : rows.length <= 1 ? 58 : 174;
78
+ return (_jsxs("div", { className: "options-editor", children: [_jsxs(GridComponent, { ref: gridRef, dataSource: rows, allowPaging: false, height: height, allowSelection: false, cssClass: "options-grid", children: [_jsxs(ColumnsDirective, { children: [_jsx(ColumnDirective, { field: "label", headerText: `${l10n.getConstant(type === 'tabs' ? 'tabLabel' : 'optionLabel')}`, width: "auto", template: labelTemplate, allowSorting: false }), _jsx(ColumnDirective, { headerText: l10n.getConstant("action"), width: "80", textAlign: "Center", allowSorting: false, template: actionTemplate })] }), _jsx(Inject, { services: [Page] })] }), _jsx("div", { className: "options-actions", children: _jsx(ButtonComponent, { cssClass: "e-outlined", onClick: handleAddRow, type: "button", children: l10n.getConstant(type === 'tabs' ? 'addNewTab' : 'addNewOption') }) })] }));
79
+ };
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import type { PropertyDefinition, PropertyValues } from '../types/ComponentProperties';
3
+ interface PropertyRendererProps {
4
+ propertiesValues: any;
5
+ property: PropertyDefinition;
6
+ initialValue: any;
7
+ existingValues: any;
8
+ onChange: (key: string, value: any) => void;
9
+ mode?: 'Simple' | 'Developer';
10
+ }
11
+ export declare const PropertyRenderer: React.FC<PropertyRendererProps>;
12
+ declare const MemoizedPropertyRenderer: React.NamedExoticComponent<PropertyRendererProps>;
13
+ interface PropertyGroupRendererProps {
14
+ groupName: string;
15
+ properties: PropertyDefinition[];
16
+ existingValues?: any;
17
+ initialValues: PropertyValues;
18
+ onChange: (key: string, value: any) => void;
19
+ mode?: 'Simple' | 'Developer';
20
+ }
21
+ export declare const PropertyGroupRenderer: React.FC<PropertyGroupRendererProps>;
22
+ export default MemoizedPropertyRenderer;