@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.
- package/README.md +148 -0
- package/index.d.ts +6 -0
- package/index.js +6 -0
- package/license +13 -0
- package/package.json +49 -0
- package/src/CheckboxGroup.d.ts +12 -0
- package/src/CheckboxGroup.js +48 -0
- package/src/CustomTabComponent.d.ts +13 -0
- package/src/CustomTabComponent.js +35 -0
- package/src/DataGridComponent.d.ts +29 -0
- package/src/DataGridComponent.js +160 -0
- package/src/FormRenderer.d.ts +63 -0
- package/src/FormRenderer.js +567 -0
- package/src/FormValidator.d.ts +479 -0
- package/src/FormValidator.js +579 -0
- package/src/StaticHtmlRenderer.d.ts +8 -0
- package/src/StaticHtmlRenderer.js +21 -0
- package/src/index.d.ts +6 -0
- package/src/index.js +6 -0
- package/src/json-converter.d.ts +170 -0
- package/src/json-converter.js +1180 -0
- package/src/parser.d.ts +99 -0
- package/src/parser.js +749 -0
- package/src/renderFormField.d.ts +19 -0
- package/src/renderFormField.js +202 -0
- package/src/types/FormSchema.d.ts +177 -0
- package/src/types/FormSchema.js +1 -0
- package/src/utils.js +60 -0
- package/styles/bootstrap-dark.css +26 -0
- package/styles/bootstrap.css +26 -0
- package/styles/bootstrap4.css +26 -0
- package/styles/bootstrap5-dark.css +26 -0
- package/styles/bootstrap5.3-dark.css +26 -0
- package/styles/bootstrap5.3.css +26 -0
- package/styles/bootstrap5.css +26 -0
- package/styles/fluent-dark.css +26 -0
- package/styles/fluent.css +26 -0
- package/styles/fluent2-dark.css +26 -0
- package/styles/fluent2.css +26 -0
- package/styles/form-renderer.base.css +473 -0
- package/styles/material-dark.css +26 -0
- package/styles/material.css +26 -0
- package/styles/material3-dark.css +26 -0
- package/styles/material3.css +26 -0
- package/styles/tailwind-dark.css +26 -0
- package/styles/tailwind.css +26 -0
- package/styles/tailwind3-dark.css +10 -0
- package/styles/tailwind3.css +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# EJ2 React Form Renderer
|
|
2
|
+
|
|
3
|
+
The `@syncfusion/ej2-react-form-renderer` package is a powerful React component for rendering dynamic, JSON-driven forms using Syncfusion's Essential® Studio React components.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **JSON-Driven Forms**: Render forms from JSON schemas (FormSchema, DataSchema, or UISchema)
|
|
8
|
+
- **20+ Field Types**: Support for textbox, textarea, number, dropdown, datepicker, checkbox, radio, switch, and more
|
|
9
|
+
- **Advanced Layouts**: Panels, tables, tabs for complex form structures
|
|
10
|
+
- **Validation**: Built-in validation with Syncfusion FormValidator
|
|
11
|
+
- **Theming**: Support for all Syncfusion themes (Material, Bootstrap, Fluent, Tailwind, etc.)
|
|
12
|
+
- **Accessibility**: WCAG 2.1 AA compliant
|
|
13
|
+
- **TypeScript Support**: Full type definitions included
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
To install `@syncfusion/ej2-react-form-renderer` and its dependencies, run:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install @syncfusion/ej2-react-form-renderer
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Basic Example
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import React from 'react';
|
|
29
|
+
import { FormRenderer } from '@syncfusion/ej2-react-form-renderer';
|
|
30
|
+
import '@syncfusion/ej2-react-form-renderer/styles/tailwind3.css'; // Choose your theme
|
|
31
|
+
|
|
32
|
+
const schema = {
|
|
33
|
+
"version": "1.0.0-beta",
|
|
34
|
+
"properties": {
|
|
35
|
+
"firstName": {
|
|
36
|
+
"id": "textbox_1773902761966_849",
|
|
37
|
+
"type": "string",
|
|
38
|
+
"label": "First Name",
|
|
39
|
+
"required": true
|
|
40
|
+
},
|
|
41
|
+
"lastName": {
|
|
42
|
+
"id": "textbox_1773902782931_266",
|
|
43
|
+
"type": "string",
|
|
44
|
+
"label": "Last Name",
|
|
45
|
+
"required": true
|
|
46
|
+
},
|
|
47
|
+
"age": {
|
|
48
|
+
"id": "number_1773902793792_571",
|
|
49
|
+
"type": "number",
|
|
50
|
+
"label": "Age",
|
|
51
|
+
"required": true
|
|
52
|
+
},
|
|
53
|
+
"dateOfBirth": {
|
|
54
|
+
"id": "datepicker_1773902807416_662",
|
|
55
|
+
"type": "string",
|
|
56
|
+
"format": "date",
|
|
57
|
+
"label": "Date of Birth",
|
|
58
|
+
"required": true
|
|
59
|
+
},
|
|
60
|
+
"submit": {
|
|
61
|
+
"id": "submit_button_initial",
|
|
62
|
+
"type": "button",
|
|
63
|
+
"label": "Submit",
|
|
64
|
+
"buttonType": "submit"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function App() {
|
|
70
|
+
const handleSubmit = (args) => {
|
|
71
|
+
console.log('Form submitted:', args.data);
|
|
72
|
+
console.log('Is valid:', args.isValid);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleChange = (args) => {
|
|
76
|
+
console.log('Field changed:', args.field, args.value);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<FormRenderer
|
|
81
|
+
dataSchema={schema}
|
|
82
|
+
onSubmit={handleSubmit}
|
|
83
|
+
onChange={handleChange}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default App;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Supported Field Types
|
|
92
|
+
|
|
93
|
+
- Basic inputs: `textbox`, `textarea`, `number`, `inputmask`
|
|
94
|
+
- Selection controls: `dropdown`, `multiselect`, `radio`, `checkbox`, `checkboxgroup`
|
|
95
|
+
- Date/time: `datepicker`, `datetimepicker`, `timepicker`, `daterange`
|
|
96
|
+
- Rich inputs: `switch` (toggle), `colorpicker`, `rating`, `signature`, `imageeditor`, `richtexteditor`, `fileupload`, `rangeslider`
|
|
97
|
+
- Layout & containers: `panel` (section), `table` (cells), `tabs`, `card`, `message` (alert), `statichtml`
|
|
98
|
+
- Data display / complex controls: `datagrid` (embedded grid)
|
|
99
|
+
- Buttons: `button`, `splitbutton` and grouped button configurations (`buttonsGroups`)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
## API Reference
|
|
103
|
+
|
|
104
|
+
### FormRenderer Props
|
|
105
|
+
|
|
106
|
+
| Prop | Type | Description |
|
|
107
|
+
|------|------|-------------|
|
|
108
|
+
| `dataSchema` | `DataSchema \| FormSchema \| string` | Form schema (JSON or object) |
|
|
109
|
+
| `uiSchema` | `UISchema \| string` | Optional UI layout schema |
|
|
110
|
+
| `layout` | `'single' \| 'twocolumn' \| 'threecolumn' \| 'fourcolumn'` | Form layout (when no UI schema) |
|
|
111
|
+
| `onSubmit` | `(args: SubmitEventArgs) => void` | Submit handler |
|
|
112
|
+
| `onChange` | `(args: ChangeEventArgs) => void` | Field change handler |
|
|
113
|
+
| `onButtonClick` | `(args: ButtonClickEventArgs) => void` | Button click handler |
|
|
114
|
+
| `created` | `() => void` | Called when form is ready |
|
|
115
|
+
| `className` | `string` | Additional CSS classes |
|
|
116
|
+
|
|
117
|
+
### FormLayout
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
export const FormLayout = {
|
|
121
|
+
SingleColumn: 'single',
|
|
122
|
+
TwoColumn: 'twocolumn',
|
|
123
|
+
ThreeColumn: 'threecolumn',
|
|
124
|
+
FourColumn: 'fourcolumn'
|
|
125
|
+
} as const;
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Schema Formats
|
|
129
|
+
|
|
130
|
+
The FormRenderer supports multiple schema formats:
|
|
131
|
+
|
|
132
|
+
1. **DataSchema**: JSON Schema-compatible format
|
|
133
|
+
2. **UISchema**: Separate UI layout configuration
|
|
134
|
+
|
|
135
|
+
## Support
|
|
136
|
+
|
|
137
|
+
Product support is available through the following channels:
|
|
138
|
+
|
|
139
|
+
- [Support Ticket](https://support.syncfusion.com/support/tickets/create) - Guaranteed response in 24 hours
|
|
140
|
+
- [Community Forum](https://www.syncfusion.com/forums)
|
|
141
|
+
|
|
142
|
+
## License and Copyright
|
|
143
|
+
|
|
144
|
+
> This is a commercial product and requires a paid license for possession or use. Syncfusion's licensed software, including this component, is subject to the terms and conditions of Syncfusion's [EULA](https://www.syncfusion.com/eula/es/). To acquire a license for React UI components, you can [purchase](https://www.syncfusion.com/sales/products) or [start a free 30-day trial](https://www.syncfusion.com/account/manage-trials/start-trials).
|
|
145
|
+
|
|
146
|
+
> A [free community license](https://www.syncfusion.com/products/communitylicense) is also available for companies and individuals whose organizations have less than $1 million USD in annual gross revenue and five or fewer developers.
|
|
147
|
+
|
|
148
|
+
© Copyright 2026 Syncfusion®, Inc. All Rights Reserved. The Syncfusion® Essential® Studio license and copyright applies to this distribution.
|
package/index.d.ts
ADDED
package/index.js
ADDED
package/license
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Syncfusion® License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Syncfusion®, Inc. All rights reserved.
|
|
4
|
+
|
|
5
|
+
Essential® JS 2 Form Renderer library is available through the Syncfusion® Essential Studio® program and can be licensed under either the Syncfusion® Community License Program or the Syncfusion® Commercial License.
|
|
6
|
+
|
|
7
|
+
To qualify for the Syncfusion® Community License Program, your organization must have annual gross revenue of less than one (1) million U.S. dollars ($1,000,000.00 USD) per year and fewer than five (5) developers. Additionally, you must agree to Syncfusion®'s terms and conditions.
|
|
8
|
+
|
|
9
|
+
If you do not meet the requirements for the community license, please reach out to sales@syncfusion.com for commercial licensing options.
|
|
10
|
+
|
|
11
|
+
You are required to obtain either a Community License or a Commercial License before using this product and must agree to comply with Syncfusion®'s terms and conditions.
|
|
12
|
+
|
|
13
|
+
The full Syncfusion® license, including terms and conditions, can be found at: https://www.syncfusion.com/content/downloads/syncfusion_license.pdf
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@syncfusion/ej2-react-form-renderer",
|
|
3
|
+
"version": "0.1.0-beta",
|
|
4
|
+
"description": "Form renderer component for rendering dynamic JSON-driven forms with Syncfusion EJ2 React components",
|
|
5
|
+
"author": "Syncfusion Inc.",
|
|
6
|
+
"license": "SEE LICENSE IN license",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"syncfusion",
|
|
9
|
+
"ej2",
|
|
10
|
+
"react",
|
|
11
|
+
"form",
|
|
12
|
+
"form-renderer",
|
|
13
|
+
"form-builder",
|
|
14
|
+
"dynamic-forms",
|
|
15
|
+
"json-forms",
|
|
16
|
+
"schema-forms",
|
|
17
|
+
"ui-components",
|
|
18
|
+
"react-components"
|
|
19
|
+
],
|
|
20
|
+
"main": "./index.js",
|
|
21
|
+
"module": "./index.js",
|
|
22
|
+
"types": "./index.d.ts",
|
|
23
|
+
"typings": "./index.d.ts",
|
|
24
|
+
"readme": "README.md",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"import": "./index.js",
|
|
28
|
+
"types": "./index.d.ts"
|
|
29
|
+
},
|
|
30
|
+
"./styles/*": "./styles/*"
|
|
31
|
+
},
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@syncfusion/ej2-react-buttons": "*",
|
|
35
|
+
"@syncfusion/ej2-react-calendars": "*",
|
|
36
|
+
"@syncfusion/ej2-react-dropdowns": "*",
|
|
37
|
+
"@syncfusion/ej2-react-grids": "*",
|
|
38
|
+
"@syncfusion/ej2-react-image-editor": "*",
|
|
39
|
+
"@syncfusion/ej2-react-inputs": "*",
|
|
40
|
+
"@syncfusion/ej2-react-layouts": "*",
|
|
41
|
+
"@syncfusion/ej2-react-lists": "*",
|
|
42
|
+
"@syncfusion/ej2-react-navigations": "*",
|
|
43
|
+
"@syncfusion/ej2-react-notifications": "*",
|
|
44
|
+
"@syncfusion/ej2-react-popups": "*",
|
|
45
|
+
"@syncfusion/ej2-react-richtexteditor": "*",
|
|
46
|
+
"@syncfusion/ej2-react-splitbuttons": "*"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {}
|
|
49
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { FormComponent } from '../FormBuilder/types/FormSchema';
|
|
3
|
+
import type { FormState } from './FormValidator';
|
|
4
|
+
interface Props {
|
|
5
|
+
component: FormComponent;
|
|
6
|
+
formState?: FormState;
|
|
7
|
+
value?: string[];
|
|
8
|
+
onChange?: (value: string[]) => void;
|
|
9
|
+
optionRef?: (el: any, idx?: number) => void;
|
|
10
|
+
}
|
|
11
|
+
declare const CheckboxGroup: React.FC<Props>;
|
|
12
|
+
export default CheckboxGroup;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { CheckBoxComponent } from '@syncfusion/ej2-react-buttons';
|
|
3
|
+
const resolveAttributes = (attributes) => {
|
|
4
|
+
if (!attributes)
|
|
5
|
+
return {};
|
|
6
|
+
if (Array.isArray(attributes)) {
|
|
7
|
+
return attributes.reduce((acc, item) => {
|
|
8
|
+
if (item && item.key && item.value !== undefined) {
|
|
9
|
+
acc[item.key] = item.value;
|
|
10
|
+
}
|
|
11
|
+
return acc;
|
|
12
|
+
}, {});
|
|
13
|
+
}
|
|
14
|
+
return attributes;
|
|
15
|
+
};
|
|
16
|
+
const CheckboxGroup = ({ component, formState, value, onChange, optionRef }) => {
|
|
17
|
+
const resolvedValue = (formState ? formState.values[component.id] : (value ?? component.defaultValue));
|
|
18
|
+
const selectedItems = Array.isArray(resolvedValue) ? resolvedValue : [];
|
|
19
|
+
const handleCheckboxChange = (option, checked) => {
|
|
20
|
+
const newValue = checked ? [...selectedItems, option] : selectedItems.filter(i => i !== option);
|
|
21
|
+
if (formState) {
|
|
22
|
+
formState.onChange(component.id, { value: newValue });
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
onChange?.(newValue);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const groupLabel = component.label && component.required ? `${component.label} *` : component.label;
|
|
29
|
+
const containerStyle = {
|
|
30
|
+
display: 'flex',
|
|
31
|
+
flexDirection: component.layoutDirection === 'row' ? 'row' : 'column',
|
|
32
|
+
gap: component.gap || '8px',
|
|
33
|
+
alignItems: 'flex-start'
|
|
34
|
+
};
|
|
35
|
+
const itemsStyle = {
|
|
36
|
+
display: 'flex',
|
|
37
|
+
flexDirection: component.layoutDirection === 'row' ? 'row' : 'column',
|
|
38
|
+
gap: component.gap || '2px',
|
|
39
|
+
width: '100%'
|
|
40
|
+
};
|
|
41
|
+
const htmlAttrs = resolveAttributes(component.htmlAttributes);
|
|
42
|
+
return (_jsx("div", { className: `checkbox-group ${component.cssClass || ''}`, style: containerStyle, ...htmlAttrs, role: "group", "aria-label": groupLabel, title: component.tooltip, children: _jsx("div", { className: "checkbox-group-items", style: itemsStyle, children: (component.options || ['Option 1']).map((option, idx) => {
|
|
43
|
+
const optionValue = typeof option === 'string' ? option : (option.value ?? option.label ?? option.key);
|
|
44
|
+
const optionLabel = typeof option === 'string' ? option : (option.label ?? option.value ?? String(optionValue));
|
|
45
|
+
return (_jsx(CheckBoxComponent, { id: `${component.id}_${idx}`, label: optionLabel, checked: selectedItems.includes(optionValue), disabled: component.disabled, labelPosition: component.checkboxLabelPosition || 'After', change: (e) => handleCheckboxChange(optionValue, e.checked), cssClass: component.cssClass, htmlAttributes: htmlAttrs, ref: optionRef ? (el) => optionRef(el, idx) : undefined }, `${component.id}-${idx}`));
|
|
46
|
+
}) }) }, `form-checkboxgroup-${component.id}`));
|
|
47
|
+
};
|
|
48
|
+
export default CheckboxGroup;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface TabItem {
|
|
3
|
+
header: string;
|
|
4
|
+
content: React.ReactNode;
|
|
5
|
+
}
|
|
6
|
+
interface CustomTabComponentProps {
|
|
7
|
+
tabs: TabItem[];
|
|
8
|
+
selectedIndex?: number;
|
|
9
|
+
onTabSelect?: (index: number) => void;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const CustomTabComponent: React.FC<CustomTabComponentProps>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useRef } from 'react';
|
|
3
|
+
export const CustomTabComponent = ({ tabs, selectedIndex = 0, onTabSelect, className = '' }) => {
|
|
4
|
+
const [activeIndex, setActiveIndex] = useState(selectedIndex);
|
|
5
|
+
const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, right: 0 });
|
|
6
|
+
const tabRefs = useRef([]);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
setActiveIndex(selectedIndex);
|
|
9
|
+
}, [selectedIndex]);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
// Update indicator position
|
|
12
|
+
const activeTab = tabRefs.current[activeIndex];
|
|
13
|
+
if (activeTab) {
|
|
14
|
+
const container = activeTab.parentElement;
|
|
15
|
+
if (container) {
|
|
16
|
+
const containerWidth = container.offsetWidth;
|
|
17
|
+
const tabLeft = activeTab.offsetLeft;
|
|
18
|
+
const tabWidth = activeTab.offsetWidth;
|
|
19
|
+
setIndicatorStyle({
|
|
20
|
+
left: tabLeft,
|
|
21
|
+
right: containerWidth - tabLeft - tabWidth
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}, [activeIndex, tabs]);
|
|
26
|
+
const handleTabClick = (index) => {
|
|
27
|
+
setActiveIndex(index);
|
|
28
|
+
onTabSelect?.(index);
|
|
29
|
+
};
|
|
30
|
+
return (_jsxs("div", { className: `form-tabs e-control e-tab e-lib e-keyboard ${className}`, "aria-disabled": "false", style: { width: '100%', height: 'auto' }, children: [_jsx("div", { className: "e-tab-header e-control e-toolbar e-lib e-keyboard", role: "tablist", "aria-disabled": "false", "aria-orientation": "horizontal", style: { height: 'auto', width: '100%' }, children: _jsxs("div", { className: "e-toolbar-items", "data-ripple": "true", children: [_jsx("div", { className: "e-indicator e-ignore", style: {
|
|
31
|
+
left: `${indicatorStyle.left}px`,
|
|
32
|
+
right: `${indicatorStyle.right}px`,
|
|
33
|
+
transition: 'left 0.3s ease, right 0.3s ease'
|
|
34
|
+
} }), tabs.map((tab, index) => (_jsx("div", { ref: el => { tabRefs.current[index] = el; }, className: `e-toolbar-item e-template ${activeIndex === index ? 'e-active' : ''}`, id: `e-item-tab_${index}`, "data-id": `tabitem_${index}`, children: _jsx("div", { className: "e-tab-wrap", tabIndex: activeIndex === index ? 0 : -1, "data-tabindex": "-1", role: "tab", "aria-selected": activeIndex === index, "aria-disabled": "false", "aria-controls": `e-content-tab_${index}`, onClick: () => handleTabClick(index), style: { cursor: 'pointer' }, children: _jsx("div", { className: "e-text-wrap", children: _jsx("div", { className: "e-tab-text", role: "presentation", children: tab.header }) }) }) }, index)))] }) }), _jsx("div", { className: "e-content e-lib e-touch", style: { height: 'auto' }, children: tabs.map((tab, index) => (_jsx("div", { className: `e-item ${activeIndex === index ? 'e-active' : ''}`, id: `e-content-tab_${index}`, role: "tabpanel", "aria-labelledby": `e-item-tab_${index}`, style: { display: activeIndex === index ? 'block' : 'none' }, children: _jsx("div", { children: tab.content }) }, index))) })] }));
|
|
35
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { GridComponentSchema } from '../FormBuilder/types/FormSchema';
|
|
3
|
+
/**
|
|
4
|
+
* DataGridRef: Public API exposed via forwardRef
|
|
5
|
+
* - getGridData(): Returns current grid data array for form submission
|
|
6
|
+
*/
|
|
7
|
+
export interface DataGridRef {
|
|
8
|
+
getGridData: () => Record<string, any>[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* DataGridComponentProps: Component props extending GridComponentSchema
|
|
12
|
+
* - onDataChange: Optional callback when grid data changes
|
|
13
|
+
* - isPreviewMode: Whether to refresh grid on column changes (for preview/renderer only)
|
|
14
|
+
*/
|
|
15
|
+
export interface DataGridComponentProps extends GridComponentSchema {
|
|
16
|
+
onDataChange?: (data: Record<string, any>[]) => void;
|
|
17
|
+
isPreviewMode?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* DataGridComponent: Wrapper around Syncfusion GridComponent
|
|
21
|
+
*
|
|
22
|
+
* Responsibilities:
|
|
23
|
+
* - Manages internal _currentData state (initialized from dataSource)
|
|
24
|
+
* - Exposes getGridData() via useImperativeHandle for form submission
|
|
25
|
+
* - Handles actionComplete events (add/edit/delete operations)
|
|
26
|
+
* - Maps GridColumn.validationRules to ColumnDirective.validationRules
|
|
27
|
+
* - Renders Syncfusion toolbar with CRUD actions based on allowAdding/Editing/Deleting
|
|
28
|
+
*/
|
|
29
|
+
export declare const DataGridComponent: React.ForwardRefExoticComponent<DataGridComponentProps & React.RefAttributes<DataGridRef>>;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useImperativeHandle, useRef, useState, useCallback, useMemo, useEffect } from 'react';
|
|
3
|
+
import { GridComponent, ColumnsDirective, ColumnDirective, Inject, Toolbar, Edit, Page, Resize, Sort, } from '@syncfusion/ej2-react-grids';
|
|
4
|
+
(() => {
|
|
5
|
+
try {
|
|
6
|
+
const filterPrefix = '[EJ2Grid.Warning]';
|
|
7
|
+
const _origWarn = console.warn;
|
|
8
|
+
const _origLog = console.log;
|
|
9
|
+
const _origError = console.error;
|
|
10
|
+
const makeFiltered = (orig) => (...args) => {
|
|
11
|
+
try {
|
|
12
|
+
const first = args[0];
|
|
13
|
+
if (typeof first === 'string' && first.includes(filterPrefix))
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
catch { }
|
|
17
|
+
orig.apply(console, args);
|
|
18
|
+
};
|
|
19
|
+
// Apply only once
|
|
20
|
+
if (!console.__ej2WarnsFiltered) {
|
|
21
|
+
console.warn = makeFiltered(_origWarn);
|
|
22
|
+
console.log = makeFiltered(_origLog);
|
|
23
|
+
console.error = makeFiltered(_origError);
|
|
24
|
+
console.__ej2WarnsFiltered = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch { }
|
|
28
|
+
})();
|
|
29
|
+
/**
|
|
30
|
+
* DataGridComponent: Wrapper around Syncfusion GridComponent
|
|
31
|
+
*
|
|
32
|
+
* Responsibilities:
|
|
33
|
+
* - Manages internal _currentData state (initialized from dataSource)
|
|
34
|
+
* - Exposes getGridData() via useImperativeHandle for form submission
|
|
35
|
+
* - Handles actionComplete events (add/edit/delete operations)
|
|
36
|
+
* - Maps GridColumn.validationRules to ColumnDirective.validationRules
|
|
37
|
+
* - Renders Syncfusion toolbar with CRUD actions based on allowAdding/Editing/Deleting
|
|
38
|
+
*/
|
|
39
|
+
export const DataGridComponent = React.forwardRef(({ id, columns, dataSource = [], height = '200px', width = '100%', pageSize = 6, allowPaging = true, allowAdding = true, allowEditing = true, allowDeleting = true, onDataChange, isPreviewMode = false }, ref) => {
|
|
40
|
+
// Internal state: maintain current grid data
|
|
41
|
+
const [_currentData, setCurrentData] = useState(Array.isArray(dataSource) ? dataSource : []);
|
|
42
|
+
const gridRef = useRef(null);
|
|
43
|
+
/**
|
|
44
|
+
* Detect column definition changes and refresh grid
|
|
45
|
+
* This ensures that changes to headerText, type, width, etc. are immediately reflected
|
|
46
|
+
* Only enabled in preview mode to avoid issues in the main form builder view
|
|
47
|
+
*/
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (gridRef.current) {
|
|
50
|
+
if (columns && !(columns.length > 0)) {
|
|
51
|
+
gridRef.current.columns = [];
|
|
52
|
+
}
|
|
53
|
+
else if (gridRef.current.columns.length === 0 && columns && columns.length == 1) {
|
|
54
|
+
gridRef.current.columns = columns;
|
|
55
|
+
}
|
|
56
|
+
setTimeout(() => {
|
|
57
|
+
if (gridRef.current?.refresh) {
|
|
58
|
+
gridRef.current.refresh();
|
|
59
|
+
}
|
|
60
|
+
}, 10);
|
|
61
|
+
}
|
|
62
|
+
}, [columns, allowAdding, allowDeleting, allowEditing, allowPaging, pageSize]);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const normalized = Array.isArray(dataSource) ? dataSource : [];
|
|
65
|
+
if (gridRef.current && isPreviewMode) {
|
|
66
|
+
setCurrentData(normalized);
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
try {
|
|
69
|
+
if (gridRef.current.dataSource !== normalized) {
|
|
70
|
+
gridRef.current.dataSource = normalized;
|
|
71
|
+
}
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
if (gridRef.current?.refresh) {
|
|
74
|
+
gridRef.current.componentRefresh();
|
|
75
|
+
}
|
|
76
|
+
}, 10);
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
}
|
|
80
|
+
}, 50);
|
|
81
|
+
}
|
|
82
|
+
}, [dataSource]);
|
|
83
|
+
/**
|
|
84
|
+
* Expose getGridData() via ref - returns current grid data for form submission
|
|
85
|
+
*/
|
|
86
|
+
useImperativeHandle(ref, () => ({
|
|
87
|
+
getGridData: () => _currentData,
|
|
88
|
+
gridRef
|
|
89
|
+
}), [_currentData]);
|
|
90
|
+
/**
|
|
91
|
+
* Handle Syncfusion grid action events (add/edit/delete)
|
|
92
|
+
* Update _currentData state when rows are modified
|
|
93
|
+
*/
|
|
94
|
+
const handleActionComplete = useCallback((args) => {
|
|
95
|
+
if (!gridRef.current)
|
|
96
|
+
return;
|
|
97
|
+
const eventType = args.requestType;
|
|
98
|
+
// Handle different action types
|
|
99
|
+
switch (eventType) {
|
|
100
|
+
case 'save':
|
|
101
|
+
case 'delete':
|
|
102
|
+
case 'add':
|
|
103
|
+
// After any data modification, sync grid data to state
|
|
104
|
+
// Use dataSource directly as it's the current working data in Syncfusion
|
|
105
|
+
if (gridRef.current.dataSource) {
|
|
106
|
+
const updatedData = Array.isArray(gridRef.current.dataSource)
|
|
107
|
+
? gridRef.current.dataSource
|
|
108
|
+
: [];
|
|
109
|
+
setCurrentData(updatedData);
|
|
110
|
+
onDataChange?.(updatedData);
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
default:
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}, [onDataChange]);
|
|
117
|
+
/**
|
|
118
|
+
* Build toolbar array dynamically based on allowAdding/Editing/Deleting
|
|
119
|
+
* Default toolbar includes: Add, Edit, Delete, Update, Cancel, ExcelExport, Search
|
|
120
|
+
*/
|
|
121
|
+
const buildToolbar = useCallback(() => {
|
|
122
|
+
const toolbarItems = [];
|
|
123
|
+
if (allowAdding) {
|
|
124
|
+
toolbarItems.push('Add');
|
|
125
|
+
}
|
|
126
|
+
if (allowEditing) {
|
|
127
|
+
toolbarItems.push('Edit');
|
|
128
|
+
}
|
|
129
|
+
if (allowDeleting) {
|
|
130
|
+
toolbarItems.push('Delete');
|
|
131
|
+
}
|
|
132
|
+
// Always include Update and Cancel for inline editing flow
|
|
133
|
+
toolbarItems.push('Update', 'Cancel');
|
|
134
|
+
return toolbarItems;
|
|
135
|
+
}, [allowAdding, allowEditing, allowDeleting]);
|
|
136
|
+
/**
|
|
137
|
+
* Map validation rules from GridColumn.validationRules to Syncfusion format
|
|
138
|
+
* Syncfusion ValidationRules type supports:
|
|
139
|
+
* - required: boolean
|
|
140
|
+
* - min/max: number (for numeric types)
|
|
141
|
+
* - minLength/maxLength: number (for string types)
|
|
142
|
+
*/
|
|
143
|
+
const mapValidationRules = useCallback((col) => {
|
|
144
|
+
if (!col.validationRules) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
return col.validationRules;
|
|
148
|
+
}, []);
|
|
149
|
+
/**
|
|
150
|
+
* Memoize toolbar to prevent unnecessary re-renders
|
|
151
|
+
*/
|
|
152
|
+
const toolbarConfig = useMemo(() => buildToolbar(), [buildToolbar]);
|
|
153
|
+
return (_jsx("div", { className: "datagrid-wrapper", style: { width, height: 'auto' }, children: _jsx("div", { className: "datagrid-grid", children: _jsxs(GridComponent, { ref: gridRef, id: id, dataSource: _currentData, allowPaging: allowPaging, pageSettings: { pageSize }, editSettings: {
|
|
154
|
+
allowEditing,
|
|
155
|
+
allowAdding,
|
|
156
|
+
allowDeleting,
|
|
157
|
+
mode: 'Normal',
|
|
158
|
+
}, toolbar: toolbarConfig, actionComplete: handleActionComplete, height: typeof height === 'string' ? height : `${height}px`, width: typeof width === 'string' ? width : `${width}px`, allowSorting: true, allowFiltering: false, allowSelection: true, children: [_jsx(ColumnsDirective, { children: columns && columns.length > 0 && columns.map((col) => (_jsx(ColumnDirective, { field: col.field, headerText: col.headerText, type: col.type || 'string', isPrimaryKey: col.isPrimaryKey || false, width: col.width, format: col.type === 'date' ? (col.format || 'MM/dd/yyyy') : col.format, editType: col.editType, validationRules: mapValidationRules(col) }, col.field))) }), _jsx(Inject, { services: [Edit, Toolbar, Page, Resize, Sort] })] }) }) }));
|
|
159
|
+
});
|
|
160
|
+
DataGridComponent.displayName = 'DataGridComponent';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form Renderer Component
|
|
3
|
+
* Renders JSON-driven forms with React FormValidator
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { type FormSchema, type ButtonClickEventArgs } from './types/FormSchema';
|
|
7
|
+
import { type LayoutNode, type UIField, type DataField } from './json-converter';
|
|
8
|
+
export interface DataSchema {
|
|
9
|
+
version: string;
|
|
10
|
+
properties: Record<string, DataField>;
|
|
11
|
+
}
|
|
12
|
+
export interface UISchema {
|
|
13
|
+
properties: Record<string, UIField>;
|
|
14
|
+
layout: LayoutNode[];
|
|
15
|
+
}
|
|
16
|
+
export interface ChangeEventArgs {
|
|
17
|
+
field: string;
|
|
18
|
+
value: any;
|
|
19
|
+
}
|
|
20
|
+
export interface SubmitEventArgs {
|
|
21
|
+
data: Record<string, any>;
|
|
22
|
+
isValid: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Form layout options (only applies when UI schema is NOT provided)
|
|
26
|
+
*/
|
|
27
|
+
export declare const FormLayout: {
|
|
28
|
+
readonly SingleColumn: "single";
|
|
29
|
+
readonly TwoColumn: "twocolumn";
|
|
30
|
+
readonly ThreeColumn: "threecolumn";
|
|
31
|
+
readonly FourColumn: "fourcolumn";
|
|
32
|
+
};
|
|
33
|
+
export type FormLayout = typeof FormLayout[keyof typeof FormLayout];
|
|
34
|
+
export interface FormRendererProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'onSubmit' | 'onClick'> {
|
|
35
|
+
/**
|
|
36
|
+
* Data Schema - can be provided as:
|
|
37
|
+
* - JSON string (parsed with UniversalJsonParser)
|
|
38
|
+
* - FormSchema object (components converted to DataSchema)
|
|
39
|
+
* - DataSchema object (used directly)
|
|
40
|
+
*/
|
|
41
|
+
dataSchema?: DataSchema | string | FormSchema | any;
|
|
42
|
+
/**
|
|
43
|
+
* UI Schema - can be provided as:
|
|
44
|
+
* - JSON string (validated and parsed)
|
|
45
|
+
* - UISchema object (used directly)
|
|
46
|
+
*/
|
|
47
|
+
uiSchema?: UISchema | string;
|
|
48
|
+
onSubmit?: (args: SubmitEventArgs) => void;
|
|
49
|
+
onChange?: (args: ChangeEventArgs) => void;
|
|
50
|
+
onButtonClick?: (args: ButtonClickEventArgs) => void;
|
|
51
|
+
className?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Form layout configuration (only applies when UI schema is NOT provided)
|
|
54
|
+
* @default FormLayout.SingleColumn
|
|
55
|
+
*/
|
|
56
|
+
layout?: FormLayout;
|
|
57
|
+
/**
|
|
58
|
+
* Called when all form elements are rendered and ready
|
|
59
|
+
*/
|
|
60
|
+
created?: () => void;
|
|
61
|
+
}
|
|
62
|
+
export declare const FormRenderer: React.ForwardRefExoticComponent<FormRendererProps & React.RefAttributes<any>>;
|
|
63
|
+
export default FormRenderer;
|