@aatulwork/customform-renderer 1.1.0 → 1.3.0
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 +415 -34
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +12 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @aatulwork/customform-renderer
|
|
2
2
|
|
|
3
3
|
A powerful, reusable form renderer component for React with Material-UI support. This package provides a dynamic form rendering system that can generate forms from JSON schemas with support for multiple field types, validation, file uploads, and more.
|
|
4
4
|
|
|
@@ -16,7 +16,7 @@ A powerful, reusable form renderer component for React with Material-UI support.
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
npm install @
|
|
19
|
+
npm install @aatulwork/customform-renderer
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
### Peer Dependencies
|
|
@@ -27,9 +27,11 @@ Make sure you have these peer dependencies installed:
|
|
|
27
27
|
npm install react react-dom @mui/material @mui/icons-material @mui/x-date-pickers react-hook-form @tanstack/react-query dayjs @ckeditor/ckeditor5-react
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
Peer versions: React ^18, MUI ^6, @mui/x-date-pickers ^7, react-hook-form ^7, @tanstack/react-query ^5, dayjs ^1.11, @ckeditor/ckeditor5-react ^11.
|
|
31
|
+
|
|
30
32
|
### CKEditor Setup
|
|
31
33
|
|
|
32
|
-
The package includes CKEditor in the `lib/ckeditor/` directory. You need to load it before using CKEditor fields.
|
|
34
|
+
The package includes CKEditor in the `lib/ckeditor/` directory. You need to load it before using CKEditor fields. **If the CKEditor field shows "CKEditor failed to load" or stays on "Loading editor...",** the script URL is not reachable—copy `lib/ckeditor/ckeditor.js` to your app’s `public/lib/ckeditor/` or set `services.ckEditorScriptPath`. See [CKEDITOR_SETUP.md](./CKEDITOR_SETUP.md) for details and troubleshooting.
|
|
33
35
|
|
|
34
36
|
**Option 1: Include in HTML (Recommended)**
|
|
35
37
|
```html
|
|
@@ -38,23 +40,34 @@ The package includes CKEditor in the `lib/ckeditor/` directory. You need to load
|
|
|
38
40
|
|
|
39
41
|
**Option 2: Copy to your public directory**
|
|
40
42
|
```bash
|
|
41
|
-
cp node_modules/@
|
|
43
|
+
cp node_modules/@aatulwork/customform-renderer/lib/ckeditor/ckeditor.js public/lib/ckeditor/
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
**Option 3: Use dynamic loading**
|
|
45
47
|
```tsx
|
|
46
|
-
import { useCKEditor } from '@
|
|
48
|
+
import { useCKEditor } from '@aatulwork/customform-renderer';
|
|
47
49
|
|
|
48
50
|
const { isReady } = useCKEditor({ autoLoad: true });
|
|
49
51
|
```
|
|
50
52
|
|
|
51
53
|
See [CKEditor Setup Guide](#ckeditor-setup) for more details.
|
|
52
54
|
|
|
55
|
+
## Package Exports
|
|
56
|
+
|
|
57
|
+
The package exports the following:
|
|
58
|
+
|
|
59
|
+
- **Components:** `FormRenderer`, `FieldRenderer`, `FormViewMode`, `FieldView`
|
|
60
|
+
- **Field components:** `TextField`, `SelectField`, `CheckboxField`, `RadioField`, `ToggleField`, `ColorField`, `DateTimePickerField`, `CKEditorField`, `FileField`, `FormReferenceField`, `ApiReferenceField`
|
|
61
|
+
- **Common:** `SimpleSelect` (and types `SimpleSelectProps`, `SimpleSelectOption`)
|
|
62
|
+
- **Types:** `FormSchema`, `FormField`, `FormSection`, `FormRendererProps`, `FieldRendererProps`, `FormServices`, `FileUploadService`, `FormReferenceService`, `ApiReferenceService`, `DateFormatterService`, `OptionItem`, `FieldType`, `FieldValidation`, `UploadedFile`, `FormColors`
|
|
63
|
+
- **Utils:** `getAllFields`, `normalizeInitialValues`, `transformFormValues`, `getDefaultValue`, `formatFileSize`, `validateFile`, `buildFieldRules`, `normalizeOptions`
|
|
64
|
+
- **CKEditor:** `loadCKEditor`, `isCKEditorAvailable`, `waitForCKEditor`, `useCKEditor`
|
|
65
|
+
- **Default services:** `defaultFileUploadService`, `defaultFormReferenceService`, `defaultApiReferenceService`, `defaultDateFormatterService` (throw if used without override; provide your own via `services`)
|
|
66
|
+
|
|
53
67
|
## Quick Start
|
|
54
68
|
|
|
55
69
|
```tsx
|
|
56
|
-
import { FormRenderer } from '@
|
|
57
|
-
import { FormSchema } from '@custom-form/renderer';
|
|
70
|
+
import { FormRenderer, FormSchema } from '@aatulwork/customform-renderer';
|
|
58
71
|
|
|
59
72
|
const formSchema: FormSchema = {
|
|
60
73
|
title: 'User Registration',
|
|
@@ -103,14 +116,22 @@ function App() {
|
|
|
103
116
|
|
|
104
117
|
```typescript
|
|
105
118
|
interface FormSchema {
|
|
119
|
+
_id?: string;
|
|
120
|
+
id?: string; // Legacy support
|
|
106
121
|
title: string;
|
|
107
|
-
name: string;
|
|
122
|
+
name: string; // Unique identifier (lowercase)
|
|
123
|
+
module?: string | null;
|
|
124
|
+
formType?: 'system' | 'custom';
|
|
125
|
+
collectionName?: string;
|
|
108
126
|
sections?: FormSection[];
|
|
109
127
|
fields?: FormField[]; // Legacy support
|
|
110
128
|
settings?: {
|
|
111
129
|
sectionDisplayMode?: 'panel' | 'stepper';
|
|
112
|
-
fieldsPerRow?: 1
|
|
130
|
+
fieldsPerRow?: number; // 1, 2, or 3
|
|
131
|
+
[key: string]: any;
|
|
113
132
|
};
|
|
133
|
+
createdAt?: string;
|
|
134
|
+
updatedAt?: string;
|
|
114
135
|
}
|
|
115
136
|
```
|
|
116
137
|
|
|
@@ -129,24 +150,32 @@ interface FormSection {
|
|
|
129
150
|
|
|
130
151
|
```typescript
|
|
131
152
|
interface FormField {
|
|
132
|
-
type: 'text' | 'email' | 'number' | 'select' | 'checkbox' | 'radio' |
|
|
133
|
-
'datepicker' | 'file' | 'ckeditor' | 'toggle' | 'color' |
|
|
153
|
+
type: 'text' | 'email' | 'number' | 'select' | 'checkbox' | 'radio' |
|
|
154
|
+
'datepicker' | 'file' | 'ckeditor' | 'toggle' | 'color' |
|
|
134
155
|
'formReference' | 'apiReference';
|
|
135
156
|
name: string;
|
|
136
157
|
label: string;
|
|
137
158
|
required?: boolean;
|
|
138
159
|
placeholder?: string;
|
|
160
|
+
allowFilter?: boolean;
|
|
139
161
|
options?: OptionItem[] | string[]; // For select/radio
|
|
140
162
|
validation?: {
|
|
141
163
|
min?: number;
|
|
142
164
|
max?: number;
|
|
143
165
|
pattern?: string;
|
|
144
|
-
maxFileSize?: number;
|
|
166
|
+
maxFileSize?: number; // For file fields (bytes)
|
|
145
167
|
allowedFileTypes?: string[]; // For file fields
|
|
146
168
|
};
|
|
147
|
-
|
|
169
|
+
// Reference fields
|
|
170
|
+
referenceFormName?: string;
|
|
171
|
+
referenceFieldName?: string;
|
|
172
|
+
apiEndpoint?: string;
|
|
173
|
+
referenceModel?: string;
|
|
174
|
+
apiLabelField?: string;
|
|
175
|
+
apiValueField?: string;
|
|
176
|
+
allowMultiple?: boolean; // For select/file fields
|
|
148
177
|
datePickerMode?: 'date' | 'datetime' | 'time'; // For datepicker
|
|
149
|
-
//
|
|
178
|
+
displayTime?: boolean; // @deprecated Use datePickerMode instead
|
|
150
179
|
}
|
|
151
180
|
```
|
|
152
181
|
|
|
@@ -187,6 +216,8 @@ interface FormField {
|
|
|
187
216
|
|
|
188
217
|
### Date Picker
|
|
189
218
|
|
|
219
|
+
Field type is `datepicker`; the exported component is `DateTimePickerField`.
|
|
220
|
+
|
|
190
221
|
```typescript
|
|
191
222
|
{
|
|
192
223
|
type: 'datepicker',
|
|
@@ -224,29 +255,375 @@ interface FormField {
|
|
|
224
255
|
|
|
225
256
|
### Reference Fields
|
|
226
257
|
|
|
258
|
+
Reference fields allow you to create dropdown selects that fetch options from other forms or external APIs. There are two types: **Form Reference** and **API Reference**.
|
|
259
|
+
|
|
260
|
+
#### Form Reference (`formReference`)
|
|
261
|
+
|
|
262
|
+
Form Reference fields fetch options from entries of another form in your system. This is useful for creating relationships between forms (e.g., selecting a user, product, or category).
|
|
263
|
+
|
|
264
|
+
**Field Configuration:**
|
|
227
265
|
```typescript
|
|
228
|
-
// Form Reference
|
|
229
266
|
{
|
|
230
267
|
type: 'formReference',
|
|
231
|
-
name: '
|
|
232
|
-
label: '
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
268
|
+
name: 'userId',
|
|
269
|
+
label: 'Select User',
|
|
270
|
+
required: true,
|
|
271
|
+
referenceFormName: 'users', // Name of the form to reference
|
|
272
|
+
referenceFieldName: 'fullName', // Field name to display as label
|
|
273
|
+
allowMultiple: false, // Allow selecting multiple items
|
|
274
|
+
placeholder: 'Select a user...'
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Required Service Setup:**
|
|
279
|
+
|
|
280
|
+
You must provide a `formReference` service that fetches options from your form entries:
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
import { FormRenderer, FormServices } from '@aatulwork/customform-renderer';
|
|
284
|
+
|
|
285
|
+
const services: FormServices = {
|
|
286
|
+
formReference: {
|
|
287
|
+
fetchOptions: async (formName: string, fieldName: string) => {
|
|
288
|
+
// Fetch entries from the referenced form
|
|
289
|
+
const response = await fetch(`/api/forms/${formName}/entries`);
|
|
290
|
+
const data = await response.json();
|
|
291
|
+
|
|
292
|
+
// Transform entries into OptionItem format
|
|
293
|
+
return data.map((entry: any) => ({
|
|
294
|
+
label: entry.payload[fieldName] || entry[fieldName] || entry._id,
|
|
295
|
+
value: entry._id,
|
|
296
|
+
}));
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
<FormRenderer
|
|
302
|
+
formSchema={formSchema}
|
|
303
|
+
services={services}
|
|
304
|
+
onSubmit={handleSubmit}
|
|
305
|
+
/>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Example: Complete Form Reference Setup**
|
|
309
|
+
|
|
310
|
+
```tsx
|
|
311
|
+
import { FormRenderer, FormServices, FormSchema } from '@aatulwork/customform-renderer';
|
|
312
|
+
|
|
313
|
+
// Define your form schema with formReference field
|
|
314
|
+
const formSchema: FormSchema = {
|
|
315
|
+
title: 'Task Assignment',
|
|
316
|
+
name: 'task-assignment',
|
|
317
|
+
sections: [
|
|
318
|
+
{
|
|
319
|
+
id: 'assignment',
|
|
320
|
+
title: 'Assignment Details',
|
|
321
|
+
fields: [
|
|
322
|
+
{
|
|
323
|
+
type: 'formReference',
|
|
324
|
+
name: 'assignedTo',
|
|
325
|
+
label: 'Assign To',
|
|
326
|
+
required: true,
|
|
327
|
+
referenceFormName: 'users', // References the 'users' form
|
|
328
|
+
referenceFieldName: 'fullName', // Displays the 'fullName' field
|
|
329
|
+
placeholder: 'Select a user...',
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
type: 'formReference',
|
|
333
|
+
name: 'project',
|
|
334
|
+
label: 'Project',
|
|
335
|
+
required: true,
|
|
336
|
+
referenceFormName: 'projects',
|
|
337
|
+
referenceFieldName: 'title',
|
|
338
|
+
allowMultiple: false,
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// Provide the formReference service
|
|
346
|
+
const services: FormServices = {
|
|
347
|
+
formReference: {
|
|
348
|
+
fetchOptions: async (formName: string, fieldName: string) => {
|
|
349
|
+
try {
|
|
350
|
+
// Example: Fetch from your API
|
|
351
|
+
const response = await fetch(`/api/forms/${formName}/entries?status=active`);
|
|
352
|
+
|
|
353
|
+
if (!response.ok) {
|
|
354
|
+
throw new Error(`Failed to fetch ${formName} entries`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const entries = await response.json();
|
|
358
|
+
|
|
359
|
+
// Transform to OptionItem format
|
|
360
|
+
return entries.map((entry: any) => ({
|
|
361
|
+
label: entry.payload?.[fieldName] || entry[fieldName] || `Entry ${entry._id}`,
|
|
362
|
+
value: entry._id,
|
|
363
|
+
}));
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.error(`Error fetching ${formName} options:`, error);
|
|
366
|
+
return [];
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
function TaskForm() {
|
|
373
|
+
const handleSubmit = async (data: Record<string, any>) => {
|
|
374
|
+
console.log('Assigned to:', data.assignedTo); // Will be the _id of selected user
|
|
375
|
+
console.log('Project:', data.project); // Will be the _id of selected project
|
|
376
|
+
// Submit to your API...
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
return (
|
|
380
|
+
<FormRenderer
|
|
381
|
+
formSchema={formSchema}
|
|
382
|
+
services={services}
|
|
383
|
+
onSubmit={handleSubmit}
|
|
384
|
+
/>
|
|
385
|
+
);
|
|
236
386
|
}
|
|
387
|
+
```
|
|
237
388
|
|
|
238
|
-
|
|
389
|
+
#### API Reference (`apiReference`)
|
|
390
|
+
|
|
391
|
+
API Reference fields fetch options from any external API endpoint. This is useful for integrating with third-party APIs or your own REST endpoints.
|
|
392
|
+
|
|
393
|
+
**Field Configuration:**
|
|
394
|
+
```typescript
|
|
239
395
|
{
|
|
240
396
|
type: 'apiReference',
|
|
241
397
|
name: 'role',
|
|
242
|
-
label: 'Role',
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
398
|
+
label: 'Select Role',
|
|
399
|
+
required: true,
|
|
400
|
+
apiEndpoint: '/api/roles', // API endpoint to fetch from
|
|
401
|
+
apiLabelField: 'name', // Field name to display as label
|
|
402
|
+
apiValueField: '_id', // Field name to use as value (default: '_id')
|
|
403
|
+
allowMultiple: false, // Allow selecting multiple items
|
|
404
|
+
placeholder: 'Select a role...'
|
|
247
405
|
}
|
|
248
406
|
```
|
|
249
407
|
|
|
408
|
+
**Required Service Setup:**
|
|
409
|
+
|
|
410
|
+
You must provide an `apiReference` service that fetches options from your API:
|
|
411
|
+
|
|
412
|
+
```tsx
|
|
413
|
+
import { FormRenderer, FormServices } from '@aatulwork/customform-renderer';
|
|
414
|
+
|
|
415
|
+
const services: FormServices = {
|
|
416
|
+
apiReference: {
|
|
417
|
+
fetchOptions: async (endpoint: string, labelField: string, valueField = '_id') => {
|
|
418
|
+
const response = await fetch(endpoint);
|
|
419
|
+
const data = await response.json();
|
|
420
|
+
|
|
421
|
+
// Handle both array responses and object responses
|
|
422
|
+
const items = Array.isArray(data) ? data : data.items || data.data || [];
|
|
423
|
+
|
|
424
|
+
return items.map((item: any) => ({
|
|
425
|
+
label: item[labelField],
|
|
426
|
+
value: item[valueField],
|
|
427
|
+
}));
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
<FormRenderer
|
|
433
|
+
formSchema={formSchema}
|
|
434
|
+
services={services}
|
|
435
|
+
onSubmit={handleSubmit}
|
|
436
|
+
/>
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Example: Complete API Reference Setup**
|
|
440
|
+
|
|
441
|
+
```tsx
|
|
442
|
+
import { FormRenderer, FormServices, FormSchema } from '@aatulwork/customform-renderer';
|
|
443
|
+
|
|
444
|
+
// Define your form schema with apiReference field
|
|
445
|
+
const formSchema: FormSchema = {
|
|
446
|
+
title: 'User Registration',
|
|
447
|
+
name: 'user-registration',
|
|
448
|
+
sections: [
|
|
449
|
+
{
|
|
450
|
+
id: 'user-info',
|
|
451
|
+
title: 'User Information',
|
|
452
|
+
fields: [
|
|
453
|
+
{
|
|
454
|
+
type: 'text',
|
|
455
|
+
name: 'firstName',
|
|
456
|
+
label: 'First Name',
|
|
457
|
+
required: true,
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
type: 'apiReference',
|
|
461
|
+
name: 'country',
|
|
462
|
+
label: 'Country',
|
|
463
|
+
required: true,
|
|
464
|
+
apiEndpoint: '/api/countries',
|
|
465
|
+
apiLabelField: 'name',
|
|
466
|
+
apiValueField: 'code',
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
type: 'apiReference',
|
|
470
|
+
name: 'role',
|
|
471
|
+
label: 'Role',
|
|
472
|
+
required: true,
|
|
473
|
+
apiEndpoint: '/api/roles',
|
|
474
|
+
apiLabelField: 'name',
|
|
475
|
+
apiValueField: '_id', // Default, can be omitted
|
|
476
|
+
},
|
|
477
|
+
],
|
|
478
|
+
},
|
|
479
|
+
],
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// Provide the apiReference service
|
|
483
|
+
const services: FormServices = {
|
|
484
|
+
apiReference: {
|
|
485
|
+
fetchOptions: async (endpoint: string, labelField: string, valueField = '_id') => {
|
|
486
|
+
try {
|
|
487
|
+
const response = await fetch(endpoint, {
|
|
488
|
+
headers: {
|
|
489
|
+
'Authorization': `Bearer ${yourAuthToken}`, // Add auth if needed
|
|
490
|
+
'Content-Type': 'application/json',
|
|
491
|
+
},
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
if (!response.ok) {
|
|
495
|
+
throw new Error(`Failed to fetch from ${endpoint}`);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const data = await response.json();
|
|
499
|
+
|
|
500
|
+
// Handle different response formats
|
|
501
|
+
const items = Array.isArray(data)
|
|
502
|
+
? data
|
|
503
|
+
: data.items || data.data || data.results || [];
|
|
504
|
+
|
|
505
|
+
// Transform to OptionItem format
|
|
506
|
+
return items.map((item: any) => ({
|
|
507
|
+
label: item[labelField] || String(item[valueField]),
|
|
508
|
+
value: item[valueField],
|
|
509
|
+
}));
|
|
510
|
+
} catch (error) {
|
|
511
|
+
console.error(`Error fetching options from ${endpoint}:`, error);
|
|
512
|
+
return [];
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
function RegistrationForm() {
|
|
519
|
+
const handleSubmit = async (data: Record<string, any>) => {
|
|
520
|
+
console.log('Country code:', data.country); // Will be the country code
|
|
521
|
+
console.log('Role ID:', data.role); // Will be the role _id
|
|
522
|
+
// Submit to your API...
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
return (
|
|
526
|
+
<FormRenderer
|
|
527
|
+
formSchema={formSchema}
|
|
528
|
+
services={services}
|
|
529
|
+
onSubmit={handleSubmit}
|
|
530
|
+
/>
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
#### Multiple Selection Support
|
|
536
|
+
|
|
537
|
+
Both reference field types support multiple selections:
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
{
|
|
541
|
+
type: 'formReference',
|
|
542
|
+
name: 'tags',
|
|
543
|
+
label: 'Tags',
|
|
544
|
+
referenceFormName: 'tags',
|
|
545
|
+
referenceFieldName: 'name',
|
|
546
|
+
allowMultiple: true, // Enable multiple selection
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
When `allowMultiple: true`, the field will return an array of selected values.
|
|
551
|
+
|
|
552
|
+
#### Advanced: Custom API with Query Parameters
|
|
553
|
+
|
|
554
|
+
You can create dynamic endpoints by modifying the service:
|
|
555
|
+
|
|
556
|
+
```tsx
|
|
557
|
+
const services: FormServices = {
|
|
558
|
+
apiReference: {
|
|
559
|
+
fetchOptions: async (endpoint: string, labelField: string, valueField = '_id') => {
|
|
560
|
+
// Add query parameters
|
|
561
|
+
const url = new URL(endpoint, window.location.origin);
|
|
562
|
+
url.searchParams.append('status', 'active');
|
|
563
|
+
url.searchParams.append('limit', '100');
|
|
564
|
+
|
|
565
|
+
const response = await fetch(url.toString());
|
|
566
|
+
const data = await response.json();
|
|
567
|
+
|
|
568
|
+
return data.map((item: any) => ({
|
|
569
|
+
label: item[labelField],
|
|
570
|
+
value: item[valueField],
|
|
571
|
+
}));
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
};
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
#### Error Handling
|
|
578
|
+
|
|
579
|
+
Both services should handle errors gracefully:
|
|
580
|
+
|
|
581
|
+
```tsx
|
|
582
|
+
const services: FormServices = {
|
|
583
|
+
formReference: {
|
|
584
|
+
fetchOptions: async (formName: string, fieldName: string) => {
|
|
585
|
+
try {
|
|
586
|
+
const response = await fetch(`/api/forms/${formName}/entries`);
|
|
587
|
+
if (!response.ok) throw new Error('Failed to fetch');
|
|
588
|
+
const data = await response.json();
|
|
589
|
+
return data.map((entry: any) => ({
|
|
590
|
+
label: entry.payload?.[fieldName] || entry._id,
|
|
591
|
+
value: entry._id,
|
|
592
|
+
}));
|
|
593
|
+
} catch (error) {
|
|
594
|
+
console.error('Form reference error:', error);
|
|
595
|
+
return []; // Return empty array on error
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
apiReference: {
|
|
600
|
+
fetchOptions: async (endpoint: string, labelField: string, valueField = '_id') => {
|
|
601
|
+
try {
|
|
602
|
+
const response = await fetch(endpoint);
|
|
603
|
+
if (!response.ok) throw new Error('Failed to fetch');
|
|
604
|
+
const data = await response.json();
|
|
605
|
+
const items = Array.isArray(data) ? data : data.items || [];
|
|
606
|
+
return items.map((item: any) => ({
|
|
607
|
+
label: item[labelField],
|
|
608
|
+
value: item[valueField],
|
|
609
|
+
}));
|
|
610
|
+
} catch (error) {
|
|
611
|
+
console.error('API reference error:', error);
|
|
612
|
+
return []; // Return empty array on error
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
};
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
#### Notes
|
|
620
|
+
|
|
621
|
+
- **Loading State**: Reference fields automatically show a loading indicator while fetching options
|
|
622
|
+
- **Disabled State**: Fields are disabled if required configuration is missing (e.g., `referenceFormName` or `apiEndpoint`)
|
|
623
|
+
- **Error Handling**: If the service throws an error or returns empty array, the field will be disabled
|
|
624
|
+
- **Caching**: Options are fetched once when the component mounts and when dependencies change
|
|
625
|
+
- **Custom Select Component**: You can provide a custom `SelectComponent` in services to use your own select component
|
|
626
|
+
|
|
250
627
|
## Service Injection
|
|
251
628
|
|
|
252
629
|
The package uses a service injection pattern to handle external dependencies. You can provide custom services for file uploads, form references, API references, and more.
|
|
@@ -254,7 +631,7 @@ The package uses a service injection pattern to handle external dependencies. Yo
|
|
|
254
631
|
### Providing Services
|
|
255
632
|
|
|
256
633
|
```tsx
|
|
257
|
-
import { FormRenderer, FormServices } from '@
|
|
634
|
+
import { FormRenderer, FormServices } from '@aatulwork/customform-renderer';
|
|
258
635
|
|
|
259
636
|
const services: FormServices = {
|
|
260
637
|
// File upload service
|
|
@@ -339,7 +716,11 @@ interface FormRendererProps {
|
|
|
339
716
|
allowResetOnValuesChange?: boolean;
|
|
340
717
|
mode?: 'edit' | 'view';
|
|
341
718
|
services?: FormServices;
|
|
719
|
+
colors?: FormColors; // Override theme colors (primary, secondary, error, etc.)
|
|
342
720
|
}
|
|
721
|
+
|
|
722
|
+
// FormColors: primary?, secondary?, error?, success?, warning?, info?,
|
|
723
|
+
// textPrimary?, textSecondary?, divider?, background?, backgroundPaper?
|
|
343
724
|
```
|
|
344
725
|
|
|
345
726
|
## View Mode
|
|
@@ -575,7 +956,7 @@ If using Vite, Create React App, or similar:
|
|
|
575
956
|
|
|
576
957
|
```bash
|
|
577
958
|
# Copy CKEditor to your public directory
|
|
578
|
-
cp node_modules/@
|
|
959
|
+
cp node_modules/@aatulwork/customform-renderer/lib/ckeditor/ckeditor.js public/lib/ckeditor/ckeditor.js
|
|
579
960
|
```
|
|
580
961
|
|
|
581
962
|
Then include in HTML:
|
|
@@ -588,7 +969,7 @@ Then include in HTML:
|
|
|
588
969
|
Use the provided hook to load CKEditor dynamically:
|
|
589
970
|
|
|
590
971
|
```tsx
|
|
591
|
-
import { useCKEditor } from '@
|
|
972
|
+
import { useCKEditor } from '@aatulwork/customform-renderer';
|
|
592
973
|
|
|
593
974
|
function App() {
|
|
594
975
|
const { isReady, isLoading, error } = useCKEditor({
|
|
@@ -622,12 +1003,12 @@ const services: FormServices = {
|
|
|
622
1003
|
### CKEditor Utilities
|
|
623
1004
|
|
|
624
1005
|
```tsx
|
|
625
|
-
import {
|
|
626
|
-
loadCKEditor,
|
|
627
|
-
isCKEditorAvailable,
|
|
1006
|
+
import {
|
|
1007
|
+
loadCKEditor,
|
|
1008
|
+
isCKEditorAvailable,
|
|
628
1009
|
waitForCKEditor,
|
|
629
|
-
useCKEditor
|
|
630
|
-
} from '@
|
|
1010
|
+
useCKEditor,
|
|
1011
|
+
} from '@aatulwork/customform-renderer';
|
|
631
1012
|
|
|
632
1013
|
// Check if available
|
|
633
1014
|
if (isCKEditorAvailable()) {
|
package/dist/index.js
CHANGED
|
@@ -568,12 +568,20 @@ var loadCKEditor = (scriptPath = "/lib/ckeditor/ckeditor.js") => {
|
|
|
568
568
|
setTimeout(() => {
|
|
569
569
|
clearInterval(checkInterval);
|
|
570
570
|
if (!window.ClassicEditor) {
|
|
571
|
-
reject(
|
|
571
|
+
reject(
|
|
572
|
+
new Error(
|
|
573
|
+
"CKEditor script loaded but ClassicEditor was not found on window. Ensure you are using the package-provided build from lib/ckeditor/ckeditor.js (see CKEDITOR_SETUP.md)."
|
|
574
|
+
)
|
|
575
|
+
);
|
|
572
576
|
}
|
|
573
577
|
}, 1e4);
|
|
574
578
|
};
|
|
575
579
|
script.onerror = () => {
|
|
576
|
-
reject(
|
|
580
|
+
reject(
|
|
581
|
+
new Error(
|
|
582
|
+
`Failed to load CKEditor from ${scriptPath}. Ensure the file exists at that URL (e.g. copy from node_modules/@aatulwork/customform-renderer/lib/ckeditor/ckeditor.js to public/lib/ckeditor/ckeditor.js) or set services.ckEditorScriptPath to a working URL.`
|
|
583
|
+
)
|
|
584
|
+
);
|
|
577
585
|
};
|
|
578
586
|
document.head.appendChild(script);
|
|
579
587
|
});
|
|
@@ -705,9 +713,10 @@ var CKEditorField = ({ field, control, defaultValue, rules, errors, setValue, fo
|
|
|
705
713
|
] });
|
|
706
714
|
}
|
|
707
715
|
if (ckEditorError || !isCKEditorReady) {
|
|
716
|
+
const message = ckEditorError?.message || `CKEditor failed to load. Script path: ${ckEditorScriptPath}. Copy ckeditor.js to public/lib/ckeditor/ or set services.ckEditorScriptPath. See CKEDITOR_SETUP.md.`;
|
|
708
717
|
return /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
|
|
709
718
|
/* @__PURE__ */ jsxRuntime.jsx(material.FormLabel, { required: field.required, error: !!errors[field.name], children: field.label }),
|
|
710
|
-
/* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { p: 2, border: "1px solid", borderColor: formColors.error, borderRadius: 1 }, children: /* @__PURE__ */ jsxRuntime.jsx(material.FormHelperText, { error: true, children:
|
|
719
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { p: 2, border: "1px solid", borderColor: formColors.error, borderRadius: 1 }, children: /* @__PURE__ */ jsxRuntime.jsx(material.FormHelperText, { error: true, children: message }) })
|
|
711
720
|
] });
|
|
712
721
|
}
|
|
713
722
|
return /* @__PURE__ */ jsxRuntime.jsx(
|