@syncfusion/ej2-react-form-renderer 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 (48) hide show
  1. package/README.md +148 -0
  2. package/index.d.ts +6 -0
  3. package/index.js +6 -0
  4. package/license +13 -0
  5. package/package.json +49 -0
  6. package/src/CheckboxGroup.d.ts +12 -0
  7. package/src/CheckboxGroup.js +48 -0
  8. package/src/CustomTabComponent.d.ts +13 -0
  9. package/src/CustomTabComponent.js +35 -0
  10. package/src/DataGridComponent.d.ts +29 -0
  11. package/src/DataGridComponent.js +160 -0
  12. package/src/FormRenderer.d.ts +63 -0
  13. package/src/FormRenderer.js +567 -0
  14. package/src/FormValidator.d.ts +479 -0
  15. package/src/FormValidator.js +579 -0
  16. package/src/StaticHtmlRenderer.d.ts +8 -0
  17. package/src/StaticHtmlRenderer.js +21 -0
  18. package/src/index.d.ts +6 -0
  19. package/src/index.js +6 -0
  20. package/src/json-converter.d.ts +170 -0
  21. package/src/json-converter.js +1180 -0
  22. package/src/parser.d.ts +99 -0
  23. package/src/parser.js +749 -0
  24. package/src/renderFormField.d.ts +19 -0
  25. package/src/renderFormField.js +202 -0
  26. package/src/types/FormSchema.d.ts +177 -0
  27. package/src/types/FormSchema.js +1 -0
  28. package/src/utils.js +60 -0
  29. package/styles/bootstrap-dark.css +26 -0
  30. package/styles/bootstrap.css +26 -0
  31. package/styles/bootstrap4.css +26 -0
  32. package/styles/bootstrap5-dark.css +26 -0
  33. package/styles/bootstrap5.3-dark.css +26 -0
  34. package/styles/bootstrap5.3.css +26 -0
  35. package/styles/bootstrap5.css +26 -0
  36. package/styles/fluent-dark.css +26 -0
  37. package/styles/fluent.css +26 -0
  38. package/styles/fluent2-dark.css +26 -0
  39. package/styles/fluent2.css +26 -0
  40. package/styles/form-renderer.base.css +473 -0
  41. package/styles/material-dark.css +26 -0
  42. package/styles/material.css +26 -0
  43. package/styles/material3-dark.css +26 -0
  44. package/styles/material3.css +26 -0
  45. package/styles/tailwind-dark.css +26 -0
  46. package/styles/tailwind.css +26 -0
  47. package/styles/tailwind3-dark.css +10 -0
  48. package/styles/tailwind3.css +26 -0
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Standalone Form Field Renderer
3
+ * Renders individual form components based on FormComponent schema
4
+ */
5
+ import React from 'react';
6
+ import { type FormComponent } from './types/FormSchema';
7
+ export interface RenderFormFieldProps {
8
+ component: FormComponent;
9
+ value?: any;
10
+ onChange: (value: any) => void;
11
+ onBlur?: () => void;
12
+ onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
13
+ ref?: any;
14
+ hasError?: boolean;
15
+ }
16
+ /**
17
+ * Renders a single form field component
18
+ */
19
+ export declare const renderFormField: ({ component, value, onChange, onBlur, ref, hasError, onClick }: RenderFormFieldProps) => React.ReactNode;
@@ -0,0 +1,202 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { TextBoxComponent, NumericTextBoxComponent, MaskedTextBoxComponent, UploaderComponent, TextAreaComponent } from '@syncfusion/ej2-react-inputs';
3
+ import { CheckBoxComponent, RadioButtonComponent, SwitchComponent, ButtonComponent, } from '@syncfusion/ej2-react-buttons';
4
+ import { SplitButtonComponent } from '@syncfusion/ej2-react-splitbuttons';
5
+ import { CheckBoxSelection, DropDownListComponent, MultiSelectComponent, } from '@syncfusion/ej2-react-dropdowns';
6
+ import { DatePickerComponent, DateTimePickerComponent, TimePickerComponent, DateRangePickerComponent, Inject, Islamic, } from '@syncfusion/ej2-react-calendars';
7
+ import { SliderComponent } from '@syncfusion/ej2-react-inputs';
8
+ import { ColorPickerComponent } from '@syncfusion/ej2-react-inputs';
9
+ import { RatingComponent } from '@syncfusion/ej2-react-inputs';
10
+ import { SignatureComponent } from '@syncfusion/ej2-react-inputs';
11
+ import { MessageComponent } from '@syncfusion/ej2-react-notifications';
12
+ import { ImageEditorComponent } from '@syncfusion/ej2-react-image-editor';
13
+ import { DataGridComponent } from './DataGridComponent';
14
+ import { RichTextEditorComponent, Toolbar, HtmlEditor, Inject as RTEInject, Link, QuickToolbar } from '@syncfusion/ej2-react-richtexteditor';
15
+ import { mapButtonStyleToClass, mapPositionToJustifyContent, resolveButtonType } from './utils';
16
+ import CheckboxGroup from './CheckboxGroup';
17
+ /**
18
+ * Converts htmlAttributes from array format to object format
19
+ */
20
+ const normalizeAttributes = (attributes) => {
21
+ if (!attributes)
22
+ return {};
23
+ if (Array.isArray(attributes)) {
24
+ return attributes.reduce((acc, item) => {
25
+ if (item.key && item.value !== undefined) {
26
+ acc[item.key] = item.value;
27
+ }
28
+ return acc;
29
+ }, {});
30
+ }
31
+ return attributes;
32
+ };
33
+ /**
34
+ * Renders a single form field component
35
+ */
36
+ export const renderFormField = ({ component, value, onChange, onBlur, ref, hasError = false, onClick }) => {
37
+ const commonProps = {
38
+ floatLabelType: (component.floatLabelType || 'Never'),
39
+ placeholder: component.placeholder || '',
40
+ enabled: !component.disabled,
41
+ };
42
+ const createValidationHandlers = (getValue) => {
43
+ const validateOn = component.validateOn || 'Blur';
44
+ const handlers = {};
45
+ // If validateOn is 'Change' or undefined, add change and blur handlers
46
+ if (!validateOn || validateOn === 'Change') {
47
+ handlers.onChange = (args) => onChange(getValue(args));
48
+ handlers.onBlur = () => onBlur?.();
49
+ }
50
+ // If validateOn is 'Blur', add change and blur handlers
51
+ if (validateOn === 'Blur') {
52
+ handlers.change = (args) => onChange(getValue(args));
53
+ handlers.blur = () => onBlur?.();
54
+ }
55
+ return handlers;
56
+ };
57
+ const htmlAttrs = normalizeAttributes(component.htmlAttributes);
58
+ const errorClass = hasError ? 'e-error' : '';
59
+ const cssClass = `${component.cssClass || ''} ${errorClass}`.trim();
60
+ switch (component.type) {
61
+ case 'textbox':
62
+ return (_jsx(TextBoxComponent, { id: component.id, type: component.multiline ? 'hidden' : component.textboxType ? component.textboxType : 'text', name: component.id, ...commonProps, value: value !== undefined ? value : (component.defaultValue || ''), multiline: component.multiline, showClearButton: component.showClearButton, autocomplete: component.autocomplete === false ? 'off' : 'on', readonly: component.readOnly || false, cssClass: cssClass, htmlAttributes: htmlAttrs, ...createValidationHandlers((args) => args.value || ''), ref: ref }, component.id));
63
+ case 'textarea':
64
+ return (_jsx(TextAreaComponent, { id: component.id, name: component.id, ...commonProps, value: value !== undefined ? value : (component.defaultValue || ''), cols: component.cols || 200, rows: component.textareaRows || 4, showClearButton: component.showClearButton || false, readonly: component.readOnly || false, maxLength: component?.maxLength || 255, cssClass: cssClass, htmlAttributes: htmlAttrs, ...createValidationHandlers((args) => args.value || ''), ref: ref }, component.id));
65
+ case 'number':
66
+ return (_jsx(NumericTextBoxComponent, { id: component.id, name: component.id, ...commonProps, value: value !== undefined ? value : (component.defaultValue || 0), decimals: component.decimals || 2, currency: component.currency, format: component.format || 'n2', step: component.step || 1, min: component.minValue || undefined, max: component.maxValue || undefined, readonly: component.readOnly || false, showClearButton: component.showClearButton || false, cssClass: cssClass, htmlAttributes: htmlAttrs, ...createValidationHandlers((args) => args.value || ''), ref: ref }, component.id));
67
+ case 'checkbox':
68
+ const checkboxLabel = component.label && component.required
69
+ ? `${component.label} *`
70
+ : component.label;
71
+ return (_jsx(CheckBoxComponent, { id: component.id, name: component.id, label: checkboxLabel, labelPosition: component.checkboxLabelPosition, checked: component.defaultValue ?? (component.checked ?? false), indeterminate: component.indeterminate || false, disabled: component.disabled, cssClass: cssClass, htmlAttributes: htmlAttrs, change: (args) => onChange(args.checked), ref: ref }, component.id));
72
+ case 'checkboxgroup':
73
+ return (_jsx(CheckboxGroup, { component: component, value: value, onChange: (v) => onChange(v), optionRef: ref }, component.id));
74
+ case 'radio':
75
+ return (_jsx("div", { id: `${component.id}-group`, className: `e-radio-group ${errorClass}`, children: (component.options || ['Option 1']).map((option, idx) => {
76
+ const optionValue = option;
77
+ const optionText = option;
78
+ return (_jsx("div", { style: { marginBottom: '8px' }, children: _jsx(RadioButtonComponent, { id: `${component.id}_${idx}_${optionValue}`, name: component.id, label: optionText, disabled: component.disabled, value: optionValue, htmlAttributes: htmlAttrs, labelPosition: component.fieldLabelPosition, checked: component.defaultValue === optionValue, cssClass: component.cssClass, change: (args) => {
79
+ if (args.value === optionValue) {
80
+ onChange(optionValue);
81
+ }
82
+ }, ref: ref ? (el) => ref(el, idx) : undefined }, component.id) }, idx));
83
+ }) }));
84
+ case 'dropdown':
85
+ return (_jsx(DropDownListComponent, { id: component.id, name: component.id, ...commonProps, cssClass: cssClass, dataSource: component.options || ["Option 1"], value: value !== undefined ? value : (component.defaultValue ?? null), showClearButton: component.showClearButton || false, readonly: component.readOnly || false, enableVirtualization: component.enableVirtualization || false, popupHeight: component.popupHeight || '300px', popupWidth: component.popupWidth || '100%', htmlAttributes: htmlAttrs, ...createValidationHandlers((args) => args.value || ''), ref: ref }, component.id));
86
+ case 'multiselect':
87
+ return (_jsx(MultiSelectComponent, { id: component.id, name: component.id, ...commonProps, value: value !== undefined ? value : (component.defaultValue ?? null), placeholder: component.placeholder, dataSource: component.options || ["Option 1"], fields: { text: 'text', value: 'value' }, showClearButton: component.showClearButton || false, readonly: component.readOnly || false, allowCustomValue: component.allowCustomValue || false, addTagOnBlur: component.addTagOnBlur || false, delimiterChar: component.delimiterChar || ',', showSelectAll: component.showSelectAll || false, mode: component.showSelectAll ? 'CheckBox' : undefined, selectAllText: component.selectAllText, unSelectAllText: component.unSelectAllText, showDropDownIcon: component.showDropDownIcon !== false, enableVirtualization: component.enableVirtualization || false, popupHeight: component.popupHeight || '300px', popupWidth: component.popupWidth || '100%', cssClass: cssClass, htmlAttributes: htmlAttrs, ...createValidationHandlers((args) => args.value || ''), ref: ref, children: _jsx(Inject, { services: [CheckBoxSelection] }) }, component.id));
88
+ case 'datepicker':
89
+ return (_jsx(DatePickerComponent, { ...commonProps, id: component.id, name: component.id, value: value !== undefined ? value : (component.defaultValue ?? null), floatLabelType: component.floatLabelType || 'Never', format: component.format || 'MM/dd/yyyy', placeholder: component.placeholder || '', readOnly: component.readOnly || false, showClearButton: component.showClearButton !== false ? true : false, showTodayButton: component.showTodayButton !== false ? true : false, calendarMode: component.calendarMode || 'Gregorian', dayHeaderFormat: component.dayHeaderFormat || 'Short', depth: component.depth || 'Month', start: component.start || 'Month', firstDayOfWeek: component.firstDayOfWeek || 0, min: component?.minDate ? new Date(component.minDate) : undefined, max: component?.maxDate ? new Date(component.maxDate) : undefined, cssClass: cssClass, htmlAttributes: htmlAttrs, ...createValidationHandlers((args) => args.value || ''), ref: ref, children: _jsx(Inject, { services: [Islamic] }) }, component.id));
90
+ case 'datetimepicker':
91
+ return (_jsx(DateTimePickerComponent, { ...commonProps, id: component.id, name: component.id, floatLabelType: component.floatLabelType || 'Never', dayHeaderFormat: component.dayHeaderFormat || 'Short', value: value !== undefined ? value : (component.defaultValue ?? null), format: component.format || 'MM/dd/yyyy hh:mm a', showClearButton: component.showClearButton !== false, showTodayButton: component.showTodayButton || false, depth: component.depth || 'Month', start: component.start || 'Month', step: component.step || 1, readonly: component.readOnly || false, min: component?.minDate ? new Date(component.minDate) : undefined, max: component?.maxDate ? new Date(component.maxDate) : undefined, minTime: component?.minTime ? new Date(`2000-01-01 ${component.minTime}`) : undefined, maxTime: component?.maxTime ? new Date(`2000-01-01 ${component.maxTime}`) : undefined, enableMask: false, cssClass: cssClass, htmlAttributes: htmlAttrs, ...createValidationHandlers((args) => args.value || ''), ref: ref, children: _jsx(Inject, { services: [Islamic] }) }, component.id));
92
+ case 'timepicker':
93
+ return (_jsx(TimePickerComponent, { id: component.id, name: component.id, floatLabelType: component.floatLabelType || 'Never', placeholder: component.placeholder || '', enabled: !component.disabled, value: value !== undefined ? value : (component.defaultValue ?? null), format: component.format || 'hh:mm a', showClearButton: component.showClearButton !== false, step: component.step || 30, readonly: component.readOnly || false, min: component?.minTime ? new Date(`2000-01-01 ${component.minTime}`) : undefined, max: component?.maxTime ? new Date(`2000-01-01 ${component.maxTime}`) : undefined, enableMask: false, cssClass: cssClass, htmlAttributes: htmlAttrs, ...createValidationHandlers((args) => args.value || ''), ref: ref }, component.id));
94
+ case 'daterange':
95
+ return (_jsx(DateRangePickerComponent, { id: component.id, name: component.id, floatLabelType: component.floatLabelType || 'Never', dayHeaderFormat: component.dayHeaderFormat || 'Short', placeholder: component.placeholder || '', enabled: !component.disabled, value: value !== undefined ? value : (component.defaultValue ?? null), depth: component.depth || 'Month', start: component.start || 'Month', min: component?.minDate ? new Date(component.minDate) : undefined, max: component?.maxDate ? new Date(component.maxDate) : undefined, format: component.format || 'MM/dd/yyyy', separator: component.separator || ' - ', showClearButton: component.showClearButton !== false, readonly: component.readOnly || false, cssClass: cssClass, htmlAttributes: htmlAttrs, ...createValidationHandlers((args) => args.value || ''), ref: ref }, component.id));
96
+ case 'switch':
97
+ return (_jsx(SwitchComponent, { id: component.id, value: component.defaultValue, name: component.id, checked: component.checked, disabled: component.disabled, onLabel: component.onLabel || '', offLabel: component.offLabel || '', cssClass: cssClass, htmlAttributes: htmlAttrs, change: (args) => onChange(args.checked), ref: ref }, component.id));
98
+ case 'button':
99
+ // If buttonsGroups is provided, render a button group in-place of a single button
100
+ const groupButtons = (component.buttonsGroups || component.buttons || []);
101
+ if (groupButtons && groupButtons.length > 0) {
102
+ const groupStyleObj = {
103
+ display: 'flex',
104
+ flexWrap: 'wrap',
105
+ alignItems: 'center',
106
+ gap: component.gap || '8px',
107
+ justifyContent: mapPositionToJustifyContent(component.position),
108
+ width: component.width || undefined,
109
+ };
110
+ return (_jsx("div", { id: component.id, role: "group", "aria-label": component.label || 'Button Group', className: component.cssClass, style: groupStyleObj, children: groupButtons.map((btn, idx) => {
111
+ const btnId = `${component.id}_${idx}`;
112
+ const btnCssClass = [
113
+ mapButtonStyleToClass(btn.style)
114
+ ].filter(Boolean).join(' ');
115
+ return (_jsx(ButtonComponent, { id: String(btnId), name: String(btnId), type: resolveButtonType(btn.type), cssClass: btnCssClass, disabled: btn.disabled || false, content: btn.label || `Button ${idx + 1}`, iconCss: btn.iconCss, iconPosition: btn.iconPosition, onClick: (event) => {
116
+ if (onClick) {
117
+ onClick(event);
118
+ }
119
+ }, ref: (el) => {
120
+ if (ref && typeof ref === 'function') {
121
+ ref(el, idx);
122
+ }
123
+ } }, String(btnId)));
124
+ }) }, `form-${component.type}-${component.id}`));
125
+ }
126
+ // Fallback: render a single ButtonComponent when no group is configured
127
+ const resolvedButtonType = resolveButtonType(component.buttonType);
128
+ const buttonCssClassList = [
129
+ mapButtonStyleToClass(component.style),
130
+ resolvedButtonType === 'submit' ? 'submit-btn form-submit-type' : '',
131
+ resolvedButtonType === 'reset' ? 'form-reset-type' : '',
132
+ component.cssClass || 'e-primary'
133
+ ].filter(Boolean).join(' ') || 'e-primary';
134
+ const buttonContainerStyle = {
135
+ display: 'flex',
136
+ justifyContent: mapPositionToJustifyContent(component.position),
137
+ width: component.width || '100%',
138
+ };
139
+ return (_jsx("div", { id: `${component.id}-container`, style: buttonContainerStyle, children: _jsx(ButtonComponent, { id: component.id, type: resolvedButtonType, cssClass: buttonCssClassList, disabled: component.disabled, iconCss: component.iconCss, iconPosition: component.iconPosition, onClick: (event) => {
140
+ if (onClick) {
141
+ onClick(event);
142
+ }
143
+ }, ref: ref, children: component.label }, component.id) }, `form-${component.type}-${component.id}`));
144
+ case 'splitbutton':
145
+ const splitButtonCssClassList = [
146
+ component.style === 'primary' ? 'e-primary' : '',
147
+ component.style === 'flat' ? '' : '',
148
+ component.style === 'information' ? 'e-info' : '',
149
+ component.style === 'error' ? 'e-danger' : '',
150
+ component.style === 'success' ? 'e-success' : '',
151
+ component.style === 'warning' ? 'e-warning' : '',
152
+ component.cssClass || ''
153
+ ].filter(Boolean).join(' ') || undefined;
154
+ const splitButtonContainerStyle = {
155
+ display: 'flex',
156
+ justifyContent: mapPositionToJustifyContent(component.position)
157
+ };
158
+ return (_jsx("div", { id: `${component.id}-container`, "aria-label": 'Splitbutton', style: splitButtonContainerStyle, children: _jsx(SplitButtonComponent, { id: component.id, content: component.label || 'Split Button', items: component.options?.map((option, index) => ({
159
+ text: option,
160
+ id: `${component.id}_option_${index}`
161
+ })) || [{ text: 'Option 1', id: `${component.id}_option_0` }], cssClass: splitButtonCssClassList, disabled: component.disabled, iconCss: component.iconCss, iconPosition: component.iconPosition || 'Left', onClick: (event) => {
162
+ if (onClick) {
163
+ onClick({ ...event, buttonId: component.id, buttonLabel: component.label });
164
+ }
165
+ }, select: (event) => {
166
+ if (onClick) {
167
+ onClick({ ...event, selectedItem: event.item?.properties?.text, buttonId: component.id, buttonLabel: component.label });
168
+ }
169
+ }, ref: ref }, component.id) }, `form-${component.type}-${component.id}`));
170
+ case 'signature':
171
+ return (_jsx(SignatureComponent, { id: component.id, backgroundColor: component.backgroundColor || '', strokeColor: component.strokeColor || '', maxStrokeWidth: component.maxValue, minStrokeWidth: component.minValue, velocity: component.velocity, disabled: component.disabled, change: (args) => onChange(args.value), ref: ref }, component.id));
172
+ case 'rating':
173
+ return (_jsx(RatingComponent, { id: component.id, name: component.id, value: value !== undefined ? value : (component.defaultValue || 0), disabled: component.disabled, cssClass: component.cssClass, allowReset: component.allowReset, itemsCount: component.itemsCount, min: component?.minValue, precision: component.precision, readOnly: component.readOnly, showTooltip: component.showRatingTooltip || false, showLabel: component.showRatingLabel, labelPosition: component.showRatingLabel ? component.ratingLabelPosition : undefined, valueChanged: (args) => onChange(args.value), ref: ref }, component.id));
174
+ case 'colorpicker':
175
+ return (_jsx(ColorPickerComponent, { id: component.id, value: value !== undefined ? value : (component.defaultValue || ''), disabled: component.disabled, enableOpacity: component.enableOpacity, showButtons: component.showButtons, showRecentColors: component.showRecentColors, noColor: component.showNoColor, cssClass: cssClass, change: (args) => onChange(args.value), ref: ref }, component.id));
176
+ case 'inputmask':
177
+ return (_jsx(MaskedTextBoxComponent, { id: component.id, name: component.id, ...commonProps, value: value !== undefined ? value : (component.defaultValue || ''), floatLabelType: component.floatLabelType || 'Never', placeholder: component.placeholder || '', enabled: !component.disabled, mask: component.mask || '000-000-0000', promptChar: component.promptChar || '_', customCharacters: component.customCharacters, readOnly: component.readOnly || false, showClearButton: component.showClearButton !== false, cssClass: cssClass, htmlAttributes: htmlAttrs, ...createValidationHandlers((args) => args.value || ''), ref: ref }, component.id));
178
+ case 'rangeslider':
179
+ return (_jsx(SliderComponent, { id: component.id, value: value !== undefined ? value : (component.defaultValue ?? 0), min: component.minValue || 0, max: component.maxValue || 100, step: component.step || 1, disabled: component.disabled, cssClass: cssClass, enabled: !component.disabled, showButtons: component.showButtons, change: (args) => onChange(args.value), ref: ref }, component.id));
180
+ case 'fileupload':
181
+ return (_jsx(UploaderComponent, { id: component.id, name: component.id, type: 'file', enabled: !component.disabled, allowedExtensions: component.allowExtensions, asyncSettings: { saveUrl: component.saveUrl, removeUrl: component.removeUrl }, buttons: component.buttons || {
182
+ "browse": "Choose File",
183
+ "clear": "Clear All",
184
+ "upload": "Upload All"
185
+ }, maxFileSize: component?.maxValue, minFileSize: component?.minValue, multiple: component.allowMultiple, showFileList: component.showFileList, htmlAttributes: htmlAttrs, cssClass: cssClass, change: (args) => onChange(args.filesData), ref: ref }, component.id));
186
+ case 'imageeditor':
187
+ return (_jsx(ImageEditorComponent, { id: component.id, disabled: component.disabled, cssClass: component.cssClass, height: (component.height || '350px').toString(), width: (component.width || '100%').toString(), ref: ref }, component.id));
188
+ case 'message':
189
+ return (_jsx(MessageComponent, { id: component.id, severity: component.severity || component.messageType || 'info', variant: component.variant, content: component.content || 'Message', showCloseIcon: component.showCloseIcon || false, showIcon: component.showIcon || false, disabled: component.disabled, cssClass: component.cssClass, ref: ref }, component.id));
190
+ case 'datagrid':
191
+ const dg = component;
192
+ return (_jsx("div", { id: dg.id, className: dg.cssClass, children: _jsx(DataGridComponent, { id: dg.id, type: 'datagrid', label: dg.label || '', columns: dg.columns || [], dataSource: value !== undefined ? value : (dg.dataSource ?? dg.dataSource ?? []), height: dg.height || '200px', width: dg.width || '100%', pageSize: dg.pageSize || 6, allowPaging: dg.allowPaging !== false, allowAdding: dg.allowAdding !== false, allowEditing: dg.allowEditing !== false, allowDeleting: dg.allowDeleting !== false, onDataChange: (data) => onChange(data), ref: ref }) }, dg.id));
193
+ case 'richtexteditor':
194
+ const toolbarSettings = {
195
+ items: ['Undo', 'Redo', '|', 'Bold', 'Italic', 'Underline', 'StrikeThrough', 'InlineCode', '|', 'CreateLink', 'Blockquote', '|', 'BulletFormatList', 'NumberFormatList', '|', 'Formats', 'Alignments', '|', 'Outdent', 'Indent', '|',
196
+ 'FontColor', 'BackgroundColor', 'FontName', 'FontSize', '|', 'LowerCase', 'UpperCase', '|', 'SourceCode']
197
+ };
198
+ return (_jsx(RichTextEditorComponent, { id: component.id, name: component.id, toolbarSettings: toolbarSettings, value: value !== undefined ? value : (component.defaultValue || ''), placeholder: component.placeholder || '', enabled: !component.disabled, readonly: component.readOnly || false, maxLength: component.maxLength || 1000, cssClass: cssClass || undefined, height: component.height || '300px', width: component.width || '100%', htmlAttributes: htmlAttrs, ref: ref, children: _jsx(RTEInject, { services: [Toolbar, Link, HtmlEditor, QuickToolbar] }) }, `${component.id}`));
199
+ default:
200
+ return (_jsxs("div", { className: "unsupported-component", children: ["Unsupported component type: ", component.type] }));
201
+ }
202
+ };
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Form Schema Type Definitions
3
+ * Copied from builder for standalone renderer
4
+ */
5
+ import type { visualMode } from "@syncfusion/ej2-react-dropdowns";
6
+ export interface ButtonClickEventArgs {
7
+ buttonId: string;
8
+ buttonLabel: string;
9
+ event: React.MouseEvent<HTMLButtonElement>;
10
+ }
11
+ export interface FormSchema {
12
+ version: string;
13
+ theme: string;
14
+ components: FormComponent[];
15
+ }
16
+ export interface FormComponent {
17
+ id: string;
18
+ type: 'textbox' | 'textarea' | 'number' | 'email' | 'url' | 'phone' | 'password' | 'checkbox' | 'checkboxgroup' | 'radio' | 'dropdown' | 'multiselect' | 'button' | 'datepicker' | 'datetimepicker' | 'timepicker' | 'daterange' | 'switch' | 'signature' | 'rating' | 'imageeditor' | 'fileupload' | 'rangeslider' | 'colorpicker' | 'inputmask' | 'message' | 'panel' | 'table' | 'tabs' | 'datagrid' | 'card' | 'splitbutton' | 'richtexteditor' | 'statichtml';
19
+ textboxType?: string;
20
+ label: string;
21
+ name: string;
22
+ placeholder?: string;
23
+ defaultValue?: any;
24
+ disabled?: boolean;
25
+ visible?: boolean;
26
+ tooltip?: string;
27
+ hideBorders?: boolean;
28
+ hideLabel?: boolean;
29
+ cssClass?: string;
30
+ multiline?: boolean;
31
+ showClearButton?: boolean;
32
+ showCloseIcon?: boolean;
33
+ showIcon?: boolean;
34
+ variant?: string;
35
+ severity?: string;
36
+ content?: string;
37
+ showTodayButton?: boolean;
38
+ htmlAttributes?: Record<string, any>;
39
+ labelPosition?: 'Top' | 'Bottom' | 'Left' | 'Right';
40
+ position?: 'center' | 'left' | 'right';
41
+ fieldLabelPosition?: 'After' | 'Before';
42
+ labelWidth?: string;
43
+ floatLabelType?: 'Never' | 'Always' | 'Auto';
44
+ readOnly?: boolean;
45
+ autocomplete?: boolean;
46
+ cols?: number;
47
+ textareaRows?: number;
48
+ checked?: boolean;
49
+ indeterminate?: boolean;
50
+ offLabel?: string;
51
+ onLabel?: string;
52
+ buttonType?: 'button' | 'submit' | 'reset';
53
+ iconCss?: string;
54
+ iconPosition?: 'Left' | 'Right';
55
+ style?: string;
56
+ buttonsGroups?: ButtonDefinition[];
57
+ gap?: string;
58
+ onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
59
+ decimals?: number;
60
+ currency?: string;
61
+ format?: string;
62
+ allowReset?: boolean;
63
+ itemsCount?: number;
64
+ precision?: number | string;
65
+ showRatingTooltip?: boolean;
66
+ showRatingLabel?: boolean;
67
+ ratingLabelPosition?: 'Top' | 'Bottom' | 'Left' | 'Right';
68
+ height?: number | null;
69
+ width?: number | null;
70
+ backgroundColor?: string;
71
+ strokeColor?: string;
72
+ maximumStrokeWidth?: number;
73
+ minimumStrokeWidth?: number;
74
+ velocity?: number;
75
+ enableOpacity?: boolean;
76
+ showNoColor?: boolean;
77
+ showButtons?: boolean;
78
+ showRecentColors?: boolean;
79
+ orientation?: 'Horizontal' | 'Vertical';
80
+ allowExtensions?: string;
81
+ saveUrl?: string;
82
+ removeUrl?: string;
83
+ buttons?: {};
84
+ allowMultiple?: boolean;
85
+ showFileList?: boolean;
86
+ calendarMode?: string;
87
+ dayHeaderFormat?: string;
88
+ depth?: string;
89
+ firstDayOfWeek?: number;
90
+ start?: string;
91
+ allowEdit?: boolean;
92
+ enabled?: boolean;
93
+ timeFormat?: string;
94
+ step?: number;
95
+ startDate?: string;
96
+ endDate?: string;
97
+ separator?: string;
98
+ mask?: string;
99
+ customCharacters?: Record<string, string>;
100
+ promptChar?: string;
101
+ allowCustomValue?: boolean;
102
+ addTagOnBlur?: boolean;
103
+ delimiterChar?: string;
104
+ visualMode?: visualMode;
105
+ showSelectAll?: boolean;
106
+ selectAllText?: string;
107
+ unSelectAllText?: string;
108
+ showDropDownIcon?: boolean;
109
+ validateOn?: 'Change' | 'Blur';
110
+ pageSize?: number;
111
+ allowPaging?: boolean;
112
+ allowAdding?: boolean;
113
+ allowEditing?: boolean;
114
+ allowDeleting?: boolean;
115
+ required?: boolean;
116
+ minLength?: number | null;
117
+ maxLength?: number | null;
118
+ min?: number | null;
119
+ minimum?: number | null;
120
+ max?: number | null;
121
+ minDate?: string;
122
+ maxDate?: string;
123
+ minValue?: number | null;
124
+ maxValue?: number | null;
125
+ minTime?: string;
126
+ maxTime?: string;
127
+ regex?: string;
128
+ validationMessage?: string;
129
+ options?: string[];
130
+ checkboxLabelPosition?: 'Before' | 'After';
131
+ layoutDirection?: 'row' | 'column';
132
+ enableHtmlSanitizer?: boolean;
133
+ fields?: Record<string, any>;
134
+ enableVirtualization?: boolean;
135
+ popupHeight?: string;
136
+ popupWidth?: string;
137
+ children?: FormComponent[];
138
+ rows?: number;
139
+ columns?: number;
140
+ messageType?: 'info' | 'success' | 'warning' | 'error';
141
+ messageContent?: string;
142
+ legend?: string;
143
+ cardTitle?: string;
144
+ cardSubtitle?: string;
145
+ columnWidths?: {
146
+ columns: number;
147
+ widths: number[];
148
+ };
149
+ tabItems?: Array<{
150
+ header: string;
151
+ content?: FormComponent[];
152
+ }>;
153
+ tableCells?: FormComponent[][][];
154
+ parentPath?: string;
155
+ properties?: Record<string, any>;
156
+ _configured?: boolean;
157
+ }
158
+ export interface ValidationRules {
159
+ [fieldName: string]: {
160
+ required?: [boolean, string];
161
+ minLength?: [number, string];
162
+ maxLength?: [number, string];
163
+ min?: [number, string];
164
+ max?: [number, string];
165
+ regex?: [string, string];
166
+ date?: [string, string];
167
+ [key: string]: any;
168
+ };
169
+ }
170
+ export interface ButtonDefinition {
171
+ label: string;
172
+ type?: 'submit' | 'reset' | 'button';
173
+ style?: 'primary' | 'flat' | 'information' | 'error' | 'success' | 'warning';
174
+ iconCss?: string;
175
+ iconPosition?: 'Left' | 'Right';
176
+ disabled?: boolean;
177
+ }
@@ -0,0 +1 @@
1
+ export {};
package/src/utils.js ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Maps button style to Syncfusion CSS classes
3
+ * @param style - Button style ('primary' | 'flat' | 'information' | 'error' | 'success' | 'warning')
4
+ * @returns Space-separated CSS class string
5
+ */
6
+ export const mapButtonStyleToClass = (style) => {
7
+ const styleMap = {
8
+ 'primary': 'e-primary',
9
+ 'flat': 'e-flat',
10
+ 'information': 'e-info',
11
+ 'error': 'e-danger',
12
+ 'success': 'e-success',
13
+ 'warning': 'e-warning'
14
+ };
15
+ return styleMap[style || 'primary'] || 'e-primary';
16
+ };
17
+ /**
18
+ * Resolves button type to valid HTML button type
19
+ * @param type - Button type ('submit' | 'reset' | 'button')
20
+ * @returns Resolved button type
21
+ */
22
+ export const resolveButtonType = (type) => {
23
+ return (type === 'submit' || type === 'reset') ? type : 'button';
24
+ };
25
+ /**
26
+ * Maps button position to CSS justifyContent value
27
+ * @param position - Button position ('Left' | 'Right' | 'Center')
28
+ * @returns CSS justifyContent value
29
+ */
30
+ export const mapPositionToJustifyContent = (position) => {
31
+ const positionMap = {
32
+ 'Left': 'flex-start',
33
+ 'Right': 'flex-end',
34
+ 'Center': 'center'
35
+ };
36
+ return positionMap[position || 'Left'] || 'flex-start';
37
+ };
38
+ export function splitWidths(input) {
39
+ const result = input.map(v => (v === null ? NaN : Number(v)));
40
+ const sumKnown = result.reduce((s, v) => s + (isNaN(v) ? 0 : v), 0);
41
+ // If knowns already >= 100, unknowns become 0
42
+ const remainingInt = Math.max(0, Math.round(100 - sumKnown));
43
+ const unknownIndexes = result.map((v, i) => (isNaN(v) ? i : -1)).filter(i => i >= 0);
44
+ const k = unknownIndexes.length;
45
+ if (k === 0) {
46
+ // nothing to split; just clamp/round knowns so they sum to 100 if you prefer, but return as-is
47
+ return result.map(v => Math.round(v));
48
+ }
49
+ // Distribute integer percentage points to avoid fractional rounding problems
50
+ const base = Math.floor(remainingInt / k);
51
+ let remainder = remainingInt - base * k;
52
+ // Fill unknowns with base + 1 for first `remainder` items
53
+ unknownIndexes.forEach((idx) => {
54
+ const add = remainder > 0 ? base + 1 : base;
55
+ result[idx] = add;
56
+ remainder = Math.max(0, remainder - 1);
57
+ });
58
+ // Convert any NaN (shouldn't remain) to 0 and round knowns
59
+ return result.map(v => (isNaN(v) ? 0 : Math.round(v)));
60
+ }
@@ -0,0 +1,26 @@
1
+ @import '@syncfusion/ej2-base/styles/bootstrap-dark.css';
2
+ @import '@syncfusion/ej2-buttons/styles/bootstrap-dark.css';
3
+ @import '@syncfusion/ej2-inputs/styles/bootstrap-dark.css';
4
+ @import '@syncfusion/ej2-dropdowns/styles/bootstrap-dark.css';
5
+ @import '@syncfusion/ej2-calendars/styles/bootstrap-dark.css';
6
+ @import '@syncfusion/ej2-react-calendars/styles/bootstrap-dark.css';
7
+ @import '@syncfusion/ej2-navigations/styles/bootstrap-dark.css';
8
+ @import '@syncfusion/ej2-notifications/styles/bootstrap-dark.css';
9
+ @import '@syncfusion/ej2-popups/styles/bootstrap-dark.css';
10
+ @import '@syncfusion/ej2-lists/styles/bootstrap-dark.css';
11
+ @import '@syncfusion/ej2-splitbuttons/styles/bootstrap-dark.css';
12
+ @import "@syncfusion/ej2-react-popups/styles/bootstrap-dark.css";
13
+ @import "@syncfusion/ej2-react-grids/styles/bootstrap-dark.css";
14
+ @import "@syncfusion/ej2-splitbuttons/styles/bootstrap-dark.css";
15
+ @import "@syncfusion/ej2-image-editor/styles/bootstrap-dark.css";
16
+ @import "@syncfusion/ej2-layouts/styles/bootstrap-dark.css";
17
+ @import '@syncfusion/ej2-richtexteditor/styles/bootstrap-dark.css';
18
+ @import 'form-renderer.base.css';
19
+
20
+ :root {
21
+ --sf-bg-color: #6e6e6e;
22
+ --sf-font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif, '-apple-system', 'BlinkMacSystemFont';
23
+ --sf-text-color: #fff;
24
+ --sf-tooltip-text-color: #fff;
25
+ --sf-layout-border-color: #1a1a1a;
26
+ }
@@ -0,0 +1,26 @@
1
+ @import '@syncfusion/ej2-base/styles/bootstrap.css';
2
+ @import '@syncfusion/ej2-buttons/styles/bootstrap.css';
3
+ @import '@syncfusion/ej2-inputs/styles/bootstrap.css';
4
+ @import '@syncfusion/ej2-dropdowns/styles/bootstrap.css';
5
+ @import '@syncfusion/ej2-calendars/styles/bootstrap.css';
6
+ @import '@syncfusion/ej2-react-calendars/styles/bootstrap.css';
7
+ @import '@syncfusion/ej2-navigations/styles/bootstrap.css';
8
+ @import '@syncfusion/ej2-notifications/styles/bootstrap.css';
9
+ @import '@syncfusion/ej2-popups/styles/bootstrap.css';
10
+ @import '@syncfusion/ej2-lists/styles/bootstrap.css';
11
+ @import '@syncfusion/ej2-splitbuttons/styles/bootstrap.css';
12
+ @import "@syncfusion/ej2-react-popups/styles/bootstrap.css";
13
+ @import "@syncfusion/ej2-react-grids/styles/bootstrap.css";
14
+ @import "@syncfusion/ej2-splitbuttons/styles/bootstrap.css";
15
+ @import "@syncfusion/ej2-image-editor/styles/bootstrap.css";
16
+ @import "@syncfusion/ej2-layouts/styles/bootstrap.css";
17
+ @import '@syncfusion/ej2-richtexteditor/styles/bootstrap.css';
18
+ @import 'form-renderer.base.css';
19
+
20
+ :root {
21
+ --sf-bg-color: #fff;
22
+ --sf-font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif, '-apple-system', 'BlinkMacSystemFont';
23
+ --sf-text-color: #333;
24
+ --sf-tooltip-text-color: #333;
25
+ --sf-layout-border-color: #000;
26
+ }
@@ -0,0 +1,26 @@
1
+ @import '@syncfusion/ej2-base/styles/bootstrap4.css';
2
+ @import '@syncfusion/ej2-buttons/styles/bootstrap4.css';
3
+ @import '@syncfusion/ej2-inputs/styles/bootstrap4.css';
4
+ @import '@syncfusion/ej2-dropdowns/styles/bootstrap4.css';
5
+ @import '@syncfusion/ej2-calendars/styles/bootstrap4.css';
6
+ @import '@syncfusion/ej2-react-calendars/styles/bootstrap4.css';
7
+ @import '@syncfusion/ej2-navigations/styles/bootstrap4.css';
8
+ @import '@syncfusion/ej2-notifications/styles/bootstrap4.css';
9
+ @import '@syncfusion/ej2-popups/styles/bootstrap4.css';
10
+ @import '@syncfusion/ej2-lists/styles/bootstrap4.css';
11
+ @import '@syncfusion/ej2-splitbuttons/styles/bootstrap4.css';
12
+ @import "@syncfusion/ej2-react-popups/styles/bootstrap4.css";
13
+ @import "@syncfusion/ej2-react-grids/styles/bootstrap4.css";
14
+ @import "@syncfusion/ej2-splitbuttons/styles/bootstrap4.css";
15
+ @import "@syncfusion/ej2-image-editor/styles/bootstrap4.css";
16
+ @import "@syncfusion/ej2-layouts/styles/bootstrap4.css";
17
+ @import '@syncfusion/ej2-richtexteditor/styles/bootstrap4.css';
18
+ @import 'form-renderer.base.css';
19
+
20
+ :root {
21
+ --sf-bg-color: #fff;
22
+ --sf-font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif, '-apple-system', 'BlinkMacSystemFont';
23
+ --sf-text-color: #000;
24
+ --sf-tooltip-text-color: #000;
25
+ --sf-layout-border-color: #343a40;
26
+ }
@@ -0,0 +1,26 @@
1
+ @import '@syncfusion/ej2-base/styles/bootstrap5-dark.css';
2
+ @import '@syncfusion/ej2-buttons/styles/bootstrap5-dark.css';
3
+ @import '@syncfusion/ej2-inputs/styles/bootstrap5-dark.css';
4
+ @import '@syncfusion/ej2-dropdowns/styles/bootstrap5-dark.css';
5
+ @import '@syncfusion/ej2-calendars/styles/bootstrap5-dark.css';
6
+ @import '@syncfusion/ej2-react-calendars/styles/bootstrap5-dark.css';
7
+ @import '@syncfusion/ej2-navigations/styles/bootstrap5-dark.css';
8
+ @import '@syncfusion/ej2-notifications/styles/bootstrap5-dark.css';
9
+ @import '@syncfusion/ej2-popups/styles/bootstrap5-dark.css';
10
+ @import '@syncfusion/ej2-lists/styles/bootstrap5-dark.css';
11
+ @import '@syncfusion/ej2-splitbuttons/styles/bootstrap5-dark.css';
12
+ @import "@syncfusion/ej2-react-popups/styles/bootstrap5-dark.css";
13
+ @import "@syncfusion/ej2-react-grids/styles/bootstrap5-dark.css";
14
+ @import "@syncfusion/ej2-splitbuttons/styles/bootstrap5-dark.css";
15
+ @import "@syncfusion/ej2-image-editor/styles/bootstrap5-dark.css";
16
+ @import "@syncfusion/ej2-layouts/styles/bootstrap5-dark.css";
17
+ @import '@syncfusion/ej2-richtexteditor/styles/bootstrap5-dark.css';
18
+ @import 'form-renderer.base.css';
19
+
20
+ :root {
21
+ --sf-bg-color: #212529;
22
+ --sf-font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', arial, 'Noto Sans', 'Liberation Sans', sans-serif, 'apple color emoji', 'Segoe UI emoji', 'Segoe UI Symbol', 'Noto color emoji';
23
+ --sf-text-color: #fff;
24
+ --sf-tooltip-text-color: #adb5bd;
25
+ --sf-layout-border-color: #343a40;
26
+ }
@@ -0,0 +1,26 @@
1
+ @import '@syncfusion/ej2-base/styles/bootstrap5.3.css';
2
+ @import '@syncfusion/ej2-buttons/styles/bootstrap5.3.css';
3
+ @import '@syncfusion/ej2-inputs/styles/bootstrap5.3.css';
4
+ @import '@syncfusion/ej2-dropdowns/styles/bootstrap5.3.css';
5
+ @import '@syncfusion/ej2-calendars/styles/bootstrap5.3.css';
6
+ @import '@syncfusion/ej2-react-calendars/styles/bootstrap5.3.css';
7
+ @import '@syncfusion/ej2-navigations/styles/bootstrap5.3.css';
8
+ @import '@syncfusion/ej2-notifications/styles/bootstrap5.3.css';
9
+ @import '@syncfusion/ej2-popups/styles/bootstrap5.3.css';
10
+ @import '@syncfusion/ej2-lists/styles/bootstrap5.3.css';
11
+ @import '@syncfusion/ej2-splitbuttons/styles/bootstrap5.3.css';
12
+ @import "@syncfusion/ej2-react-popups/styles/bootstrap5.3.css";
13
+ @import "@syncfusion/ej2-react-grids/styles/bootstrap5.3.css";
14
+ @import "@syncfusion/ej2-splitbuttons/styles/bootstrap5.3.css";
15
+ @import "@syncfusion/ej2-image-editor/styles/bootstrap5.3.css";
16
+ @import "@syncfusion/ej2-layouts/styles/bootstrap5.3.css";
17
+ @import '@syncfusion/ej2-richtexteditor/styles/bootstrap5.3.css';
18
+ @import 'form-renderer.base.css';
19
+
20
+ :root {
21
+ --sf-bg-color: #212529;
22
+ --sf-font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', arial, 'Noto Sans', 'Liberation Sans', sans-serif, 'apple color emoji', 'Segoe UI emoji', 'Segoe UI Symbol', 'Noto color emoji';
23
+ --sf-text-color: #dee2e6;
24
+ --sf-tooltip-text-color: #adb5bd;
25
+ --sf-layout-border-color: #495057;
26
+ }
@@ -0,0 +1,26 @@
1
+ @import '@syncfusion/ej2-base/styles/bootstrap5.3.css';
2
+ @import '@syncfusion/ej2-buttons/styles/bootstrap5.3.css';
3
+ @import '@syncfusion/ej2-inputs/styles/bootstrap5.3.css';
4
+ @import '@syncfusion/ej2-dropdowns/styles/bootstrap5.3.css';
5
+ @import '@syncfusion/ej2-calendars/styles/bootstrap5.3.css';
6
+ @import '@syncfusion/ej2-react-calendars/styles/bootstrap5.3.css';
7
+ @import '@syncfusion/ej2-navigations/styles/bootstrap5.3.css';
8
+ @import '@syncfusion/ej2-notifications/styles/bootstrap5.3.css';
9
+ @import '@syncfusion/ej2-popups/styles/bootstrap5.3.css';
10
+ @import '@syncfusion/ej2-lists/styles/bootstrap5.3.css';
11
+ @import '@syncfusion/ej2-splitbuttons/styles/bootstrap5.3.css';
12
+ @import "@syncfusion/ej2-react-popups/styles/bootstrap5.3.css";
13
+ @import "@syncfusion/ej2-react-grids/styles/bootstrap5.3.css";
14
+ @import "@syncfusion/ej2-splitbuttons/styles/bootstrap5.3.css";
15
+ @import "@syncfusion/ej2-image-editor/styles/bootstrap5.3.css";
16
+ @import "@syncfusion/ej2-layouts/styles/bootstrap5.3.css";
17
+ @import '@syncfusion/ej2-richtexteditor/styles/bootstrap5.3.css';
18
+ @import 'form-renderer.base.css';
19
+
20
+ :root {
21
+ --sf-bg-color: #fff;
22
+ --sf-font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', arial, 'Noto Sans', 'Liberation Sans', sans-serif, 'apple color emoji', 'Segoe UI emoji', 'Segoe UI Symbol', 'Noto color emoji';
23
+ --sf-text-color: #212529;
24
+ --sf-tooltip-text-color: #6c757d;
25
+ --sf-layout-border-color: #dee2e6;
26
+ }