@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
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
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @syncfusion/ej2-react-form-renderer
3
+ * Form Renderer Component - Dynamic JSON-driven forms
4
+ */
5
+
6
+ export * from './src/index.js';
package/index.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @syncfusion/ej2-react-form-renderer
3
+ * Form Renderer Component - Dynamic JSON-driven forms
4
+ */
5
+
6
+ export * from './src/index.js';
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;