@aatulwork/customform-renderer 1.9.0 → 1.10.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/dist/fields-BM8DDh8m.d.mts +186 -0
- package/dist/fields-BM8DDh8m.d.ts +186 -0
- package/dist/fields.d.mts +3 -0
- package/dist/fields.d.ts +3 -0
- package/dist/fields.js +1230 -0
- package/dist/fields.js.map +1 -0
- package/dist/fields.mjs +1210 -0
- package/dist/fields.mjs.map +1 -0
- package/dist/index.d.mts +10 -186
- package/dist/index.d.ts +10 -186
- package/dist/index.js +81 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +82 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -1
package/dist/fields.js
ADDED
|
@@ -0,0 +1,1230 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var reactHookForm = require('react-hook-form');
|
|
4
|
+
var material = require('@mui/material');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var react = require('react');
|
|
7
|
+
var RefreshIcon = require('@mui/icons-material/Refresh');
|
|
8
|
+
var DatePicker = require('@mui/x-date-pickers/DatePicker');
|
|
9
|
+
var DateTimePicker = require('@mui/x-date-pickers/DateTimePicker');
|
|
10
|
+
var TimePicker = require('@mui/x-date-pickers/TimePicker');
|
|
11
|
+
var LocalizationProvider = require('@mui/x-date-pickers/LocalizationProvider');
|
|
12
|
+
var AdapterDayjs = require('@mui/x-date-pickers/AdapterDayjs');
|
|
13
|
+
var dayjs = require('dayjs');
|
|
14
|
+
var ckeditor5React = require('@ckeditor/ckeditor5-react');
|
|
15
|
+
var CloseIcon = require('@mui/icons-material/Close');
|
|
16
|
+
var CloudUploadIcon = require('@mui/icons-material/CloudUpload');
|
|
17
|
+
var InsertDriveFileIcon = require('@mui/icons-material/InsertDriveFile');
|
|
18
|
+
|
|
19
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
20
|
+
|
|
21
|
+
var RefreshIcon__default = /*#__PURE__*/_interopDefault(RefreshIcon);
|
|
22
|
+
var dayjs__default = /*#__PURE__*/_interopDefault(dayjs);
|
|
23
|
+
var CloseIcon__default = /*#__PURE__*/_interopDefault(CloseIcon);
|
|
24
|
+
var CloudUploadIcon__default = /*#__PURE__*/_interopDefault(CloudUploadIcon);
|
|
25
|
+
var InsertDriveFileIcon__default = /*#__PURE__*/_interopDefault(InsertDriveFileIcon);
|
|
26
|
+
|
|
27
|
+
// src/components/fields/TextField.tsx
|
|
28
|
+
var TextField = ({ field, control, defaultValue, rules, errors }) => {
|
|
29
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
30
|
+
reactHookForm.Controller,
|
|
31
|
+
{
|
|
32
|
+
name: field.name,
|
|
33
|
+
control,
|
|
34
|
+
defaultValue,
|
|
35
|
+
rules,
|
|
36
|
+
render: ({ field: formField }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
37
|
+
material.FormControl,
|
|
38
|
+
{
|
|
39
|
+
fullWidth: true,
|
|
40
|
+
required: field.required,
|
|
41
|
+
error: !!errors[field.name],
|
|
42
|
+
children: [
|
|
43
|
+
/* @__PURE__ */ jsxRuntime.jsxs(material.FormLabel, { children: [
|
|
44
|
+
" ",
|
|
45
|
+
field.label
|
|
46
|
+
] }),
|
|
47
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
48
|
+
material.TextField,
|
|
49
|
+
{
|
|
50
|
+
...formField,
|
|
51
|
+
type: field.type === "number" ? "number" : field.type,
|
|
52
|
+
placeholder: field.placeholder || "Enter value",
|
|
53
|
+
fullWidth: true,
|
|
54
|
+
size: "small",
|
|
55
|
+
required: field.required,
|
|
56
|
+
error: !!errors[field.name],
|
|
57
|
+
helperText: errors[field.name]?.message,
|
|
58
|
+
inputProps: field.type === "number" ? {
|
|
59
|
+
min: field.validation?.min,
|
|
60
|
+
max: field.validation?.max
|
|
61
|
+
} : void 0
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
},
|
|
68
|
+
field.name
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/utils/fieldHelpers.ts
|
|
73
|
+
var formatFileSize = (bytes) => {
|
|
74
|
+
if (bytes === 0) return "0 Bytes";
|
|
75
|
+
const k = 1024;
|
|
76
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
77
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
78
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
|
|
79
|
+
};
|
|
80
|
+
var validateFile = (file, field) => {
|
|
81
|
+
if (field.validation?.allowedFileTypes && field.validation.allowedFileTypes.length > 0) {
|
|
82
|
+
const fileExtension = file.name.split(".").pop()?.toLowerCase();
|
|
83
|
+
const allowedTypes = field.validation.allowedFileTypes.map((t) => t.toLowerCase().replace(".", ""));
|
|
84
|
+
if (!fileExtension || !allowedTypes.includes(fileExtension)) {
|
|
85
|
+
return `File type not allowed. Allowed types: ${field.validation.allowedFileTypes.join(", ")}`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (field.validation?.maxFileSize) {
|
|
89
|
+
if (file.size > field.validation.maxFileSize) {
|
|
90
|
+
const maxSizeFormatted = formatFileSize(field.validation.maxFileSize);
|
|
91
|
+
return `File size exceeds maximum allowed size of ${maxSizeFormatted}`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
};
|
|
96
|
+
var buildFieldRules = (field) => {
|
|
97
|
+
const rules = {};
|
|
98
|
+
if (field.required) {
|
|
99
|
+
rules.required = `${field.label} is required`;
|
|
100
|
+
}
|
|
101
|
+
if (field.type === "file") {
|
|
102
|
+
rules.validate = (value) => {
|
|
103
|
+
if (field.required) {
|
|
104
|
+
if (field.allowMultiple) {
|
|
105
|
+
const files = Array.isArray(value) ? value : [];
|
|
106
|
+
if (files.length === 0) {
|
|
107
|
+
return `${field.label} is required`;
|
|
108
|
+
}
|
|
109
|
+
const hasValidFiles = files.some(
|
|
110
|
+
(file) => file && typeof file === "object" && ("fileName" in file || "fileUrl" in file)
|
|
111
|
+
);
|
|
112
|
+
if (!hasValidFiles) {
|
|
113
|
+
return `${field.label} is required`;
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
if (!value || typeof value === "object" && !("fileName" in value || "fileUrl" in value)) {
|
|
117
|
+
return `${field.label} is required`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if ((field.type === "select" || field.type === "formReference" || field.type === "apiReference") && field.allowMultiple) {
|
|
125
|
+
rules.validate = (value) => {
|
|
126
|
+
const values = value;
|
|
127
|
+
if (!values || values.length === 0) {
|
|
128
|
+
if (field.required) {
|
|
129
|
+
return `${field.label} is required`;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return rules;
|
|
137
|
+
};
|
|
138
|
+
var normalizeOptions = (options) => {
|
|
139
|
+
if (!options) return [];
|
|
140
|
+
return options.map((opt) => {
|
|
141
|
+
if (typeof opt === "string") {
|
|
142
|
+
return { label: opt, value: opt };
|
|
143
|
+
}
|
|
144
|
+
return opt;
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
var SimpleSelect = ({
|
|
148
|
+
label,
|
|
149
|
+
value,
|
|
150
|
+
onChange,
|
|
151
|
+
options,
|
|
152
|
+
placeholder,
|
|
153
|
+
helperText,
|
|
154
|
+
fullWidth = true,
|
|
155
|
+
size = "small",
|
|
156
|
+
required = false,
|
|
157
|
+
error = false,
|
|
158
|
+
disabled = false,
|
|
159
|
+
multiple = false,
|
|
160
|
+
isLoading = false,
|
|
161
|
+
filterable = true,
|
|
162
|
+
filterPlaceholder = "Filter...",
|
|
163
|
+
refreshable = false,
|
|
164
|
+
onRefresh
|
|
165
|
+
}) => {
|
|
166
|
+
const [filterText, setFilterText] = react.useState("");
|
|
167
|
+
const handleChange = (event) => {
|
|
168
|
+
const val = event.target.value;
|
|
169
|
+
if (val === "__refresh__") {
|
|
170
|
+
onRefresh?.();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
onChange(val);
|
|
174
|
+
};
|
|
175
|
+
const filteredOptions = filterable && filterText ? options.filter(
|
|
176
|
+
(opt) => String(opt.label).toLowerCase().includes(filterText.toLowerCase())
|
|
177
|
+
) : options;
|
|
178
|
+
const handleFilterChange = (e) => {
|
|
179
|
+
setFilterText(e.target.value);
|
|
180
|
+
};
|
|
181
|
+
const handleClose = () => {
|
|
182
|
+
setFilterText("");
|
|
183
|
+
};
|
|
184
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(material.FormControl, { fullWidth, size, required, error, disabled, children: [
|
|
185
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.InputLabel, { children: label }),
|
|
186
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
187
|
+
material.Select,
|
|
188
|
+
{
|
|
189
|
+
value: value ?? (multiple ? [] : ""),
|
|
190
|
+
onChange: handleChange,
|
|
191
|
+
onClose: handleClose,
|
|
192
|
+
input: /* @__PURE__ */ jsxRuntime.jsx(material.OutlinedInput, { label }),
|
|
193
|
+
MenuProps: {
|
|
194
|
+
autoFocus: false
|
|
195
|
+
},
|
|
196
|
+
multiple,
|
|
197
|
+
disabled: disabled || isLoading,
|
|
198
|
+
endAdornment: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(material.CircularProgress, { size: 20 }) : refreshable && onRefresh ? /* @__PURE__ */ jsxRuntime.jsx(material.InputAdornment, { position: "end", sx: { mr: 2 }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
199
|
+
material.IconButton,
|
|
200
|
+
{
|
|
201
|
+
size: "small",
|
|
202
|
+
onClick: (e) => {
|
|
203
|
+
e.stopPropagation();
|
|
204
|
+
onRefresh();
|
|
205
|
+
},
|
|
206
|
+
disabled,
|
|
207
|
+
"aria-label": "Refresh options",
|
|
208
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(RefreshIcon__default.default, { fontSize: "small" })
|
|
209
|
+
}
|
|
210
|
+
) }) : void 0,
|
|
211
|
+
renderValue: (selected) => {
|
|
212
|
+
if (multiple) {
|
|
213
|
+
const selectedValues = selected;
|
|
214
|
+
if (selectedValues.length === 0) {
|
|
215
|
+
return /* @__PURE__ */ jsxRuntime.jsx("em", { children: placeholder || "Select..." });
|
|
216
|
+
}
|
|
217
|
+
return /* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { display: "flex", flexWrap: "wrap", gap: 0.5 }, children: selectedValues.map((val) => {
|
|
218
|
+
const option2 = options.find((opt) => opt.value === val);
|
|
219
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
220
|
+
material.Chip,
|
|
221
|
+
{
|
|
222
|
+
label: option2?.label || val,
|
|
223
|
+
size: "small"
|
|
224
|
+
},
|
|
225
|
+
val
|
|
226
|
+
);
|
|
227
|
+
}) });
|
|
228
|
+
}
|
|
229
|
+
const option = options.find((opt) => opt.value === selected);
|
|
230
|
+
return option?.label || selected || /* @__PURE__ */ jsxRuntime.jsx("em", { children: placeholder || "Select..." });
|
|
231
|
+
},
|
|
232
|
+
children: [
|
|
233
|
+
filterable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
234
|
+
material.ListSubheader,
|
|
235
|
+
{
|
|
236
|
+
sx: { py: 1 },
|
|
237
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
238
|
+
onClick: (e) => e.stopPropagation(),
|
|
239
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
240
|
+
material.TextField,
|
|
241
|
+
{
|
|
242
|
+
size: "small",
|
|
243
|
+
fullWidth: true,
|
|
244
|
+
placeholder: filterPlaceholder,
|
|
245
|
+
value: filterText,
|
|
246
|
+
onChange: handleFilterChange,
|
|
247
|
+
variant: "outlined",
|
|
248
|
+
sx: { mt: -0.5 },
|
|
249
|
+
autoFocus: true
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
),
|
|
254
|
+
filteredOptions.map((option) => /* @__PURE__ */ jsxRuntime.jsx(material.MenuItem, { value: option.value, children: option.label }, option.value))
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
),
|
|
258
|
+
helperText && /* @__PURE__ */ jsxRuntime.jsx(material.FormHelperText, { children: helperText })
|
|
259
|
+
] });
|
|
260
|
+
};
|
|
261
|
+
var SelectField = ({ field, control, defaultValue, rules, errors }) => {
|
|
262
|
+
const isMultiple = field.allowMultiple || false;
|
|
263
|
+
const normalizedOptions = normalizeOptions(field.options);
|
|
264
|
+
const selectOptions = react.useMemo(() => {
|
|
265
|
+
return normalizedOptions.map((opt) => ({
|
|
266
|
+
value: String(opt.value),
|
|
267
|
+
label: opt.label
|
|
268
|
+
}));
|
|
269
|
+
}, [normalizedOptions]);
|
|
270
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
271
|
+
reactHookForm.Controller,
|
|
272
|
+
{
|
|
273
|
+
name: field.name,
|
|
274
|
+
control,
|
|
275
|
+
defaultValue,
|
|
276
|
+
rules,
|
|
277
|
+
render: ({ field: formField }) => {
|
|
278
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
279
|
+
SimpleSelect,
|
|
280
|
+
{
|
|
281
|
+
label: field.label,
|
|
282
|
+
value: formField.value,
|
|
283
|
+
onChange: (value) => {
|
|
284
|
+
formField.onChange(value);
|
|
285
|
+
},
|
|
286
|
+
options: selectOptions,
|
|
287
|
+
placeholder: field.placeholder || "Select...",
|
|
288
|
+
helperText: errors[field.name]?.message,
|
|
289
|
+
fullWidth: true,
|
|
290
|
+
size: "small",
|
|
291
|
+
required: field.required,
|
|
292
|
+
error: !!errors[field.name],
|
|
293
|
+
disabled: false,
|
|
294
|
+
multiple: isMultiple
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
field.name
|
|
300
|
+
);
|
|
301
|
+
};
|
|
302
|
+
var CheckboxField = ({ field, control, defaultValue, rules }) => {
|
|
303
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
304
|
+
reactHookForm.Controller,
|
|
305
|
+
{
|
|
306
|
+
name: field.name,
|
|
307
|
+
control,
|
|
308
|
+
defaultValue,
|
|
309
|
+
rules,
|
|
310
|
+
render: ({ field: formField }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
311
|
+
material.FormControlLabel,
|
|
312
|
+
{
|
|
313
|
+
control: /* @__PURE__ */ jsxRuntime.jsx(
|
|
314
|
+
material.Checkbox,
|
|
315
|
+
{
|
|
316
|
+
...formField,
|
|
317
|
+
checked: formField.value || false,
|
|
318
|
+
size: "small"
|
|
319
|
+
}
|
|
320
|
+
),
|
|
321
|
+
label: field.label
|
|
322
|
+
}
|
|
323
|
+
)
|
|
324
|
+
},
|
|
325
|
+
field.name
|
|
326
|
+
);
|
|
327
|
+
};
|
|
328
|
+
var RadioField = ({ field, control, defaultValue, rules, errors }) => {
|
|
329
|
+
const normalizedOptions = normalizeOptions(field.options);
|
|
330
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
331
|
+
reactHookForm.Controller,
|
|
332
|
+
{
|
|
333
|
+
name: field.name,
|
|
334
|
+
control,
|
|
335
|
+
defaultValue,
|
|
336
|
+
rules,
|
|
337
|
+
render: ({ field: formField }) => /* @__PURE__ */ jsxRuntime.jsxs(material.FormControl, { component: "fieldset", required: field.required, error: !!errors[field.name], children: [
|
|
338
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.FormLabel, { required: field.required, error: !!errors[field.name], children: field.label }),
|
|
339
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.RadioGroup, { ...formField, row: true, children: normalizedOptions.map((option, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
340
|
+
material.FormControlLabel,
|
|
341
|
+
{
|
|
342
|
+
value: option.value,
|
|
343
|
+
control: /* @__PURE__ */ jsxRuntime.jsx(material.Radio, { size: "small" }),
|
|
344
|
+
label: option.label
|
|
345
|
+
},
|
|
346
|
+
index
|
|
347
|
+
)) })
|
|
348
|
+
] })
|
|
349
|
+
},
|
|
350
|
+
field.name
|
|
351
|
+
);
|
|
352
|
+
};
|
|
353
|
+
var ToggleField = ({ field, control, defaultValue, rules, errors }) => {
|
|
354
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
355
|
+
reactHookForm.Controller,
|
|
356
|
+
{
|
|
357
|
+
name: field.name,
|
|
358
|
+
control,
|
|
359
|
+
defaultValue,
|
|
360
|
+
rules,
|
|
361
|
+
render: ({ field: formField }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
362
|
+
material.FormControlLabel,
|
|
363
|
+
{
|
|
364
|
+
control: /* @__PURE__ */ jsxRuntime.jsx(
|
|
365
|
+
material.Switch,
|
|
366
|
+
{
|
|
367
|
+
...formField,
|
|
368
|
+
checked: formField.value || false,
|
|
369
|
+
size: "medium"
|
|
370
|
+
}
|
|
371
|
+
),
|
|
372
|
+
label: /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
|
|
373
|
+
/* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "body2", children: [
|
|
374
|
+
field.label,
|
|
375
|
+
" ",
|
|
376
|
+
field.required && "*"
|
|
377
|
+
] }),
|
|
378
|
+
errors[field.name] && /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "caption", color: "error", sx: { display: "block", mt: 0.5 }, children: errors[field.name]?.message })
|
|
379
|
+
] })
|
|
380
|
+
}
|
|
381
|
+
)
|
|
382
|
+
},
|
|
383
|
+
field.name
|
|
384
|
+
);
|
|
385
|
+
};
|
|
386
|
+
var ColorField = ({ field, control, defaultValue, rules, errors }) => {
|
|
387
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
388
|
+
reactHookForm.Controller,
|
|
389
|
+
{
|
|
390
|
+
name: field.name,
|
|
391
|
+
control,
|
|
392
|
+
defaultValue,
|
|
393
|
+
rules,
|
|
394
|
+
render: ({ field: formField }) => /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { display: "flex", alignItems: "center", gap: 2, width: "100%" }, children: [
|
|
395
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
396
|
+
material.FormLabel,
|
|
397
|
+
{
|
|
398
|
+
required: field.required,
|
|
399
|
+
error: !!errors[field.name],
|
|
400
|
+
children: field.label
|
|
401
|
+
}
|
|
402
|
+
),
|
|
403
|
+
/* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { display: "flex", flexDirection: "column", flex: 1, maxWidth: 200 }, children: [
|
|
404
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
405
|
+
material.Input,
|
|
406
|
+
{
|
|
407
|
+
...formField,
|
|
408
|
+
type: "color",
|
|
409
|
+
sx: {
|
|
410
|
+
width: "20%",
|
|
411
|
+
height: "40px",
|
|
412
|
+
cursor: "pointer",
|
|
413
|
+
border: errors[field.name] ? "1px solid red" : "1px solid rgba(0, 0, 0, 0.23)",
|
|
414
|
+
borderRadius: "4px",
|
|
415
|
+
padding: "1px"
|
|
416
|
+
},
|
|
417
|
+
inputProps: {
|
|
418
|
+
style: {
|
|
419
|
+
height: "100%",
|
|
420
|
+
cursor: "pointer"
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
),
|
|
425
|
+
errors[field.name] && /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "caption", color: "error", sx: { mt: 0.5 }, children: errors[field.name]?.message })
|
|
426
|
+
] })
|
|
427
|
+
] })
|
|
428
|
+
},
|
|
429
|
+
field.name
|
|
430
|
+
);
|
|
431
|
+
};
|
|
432
|
+
var textFieldSlotProps = (field, errors) => ({
|
|
433
|
+
fullWidth: true,
|
|
434
|
+
size: "small",
|
|
435
|
+
required: field.required,
|
|
436
|
+
error: !!errors[field.name],
|
|
437
|
+
helperText: errors[field.name]?.message
|
|
438
|
+
});
|
|
439
|
+
function resolveDatePickerMode(field) {
|
|
440
|
+
if (field.datePickerMode) return field.datePickerMode;
|
|
441
|
+
return field?.displayTime ? "datetime" : "date";
|
|
442
|
+
}
|
|
443
|
+
var DateTimePickerField = ({ field, control, defaultValue, rules, errors }) => {
|
|
444
|
+
const mode = resolveDatePickerMode(field);
|
|
445
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
446
|
+
reactHookForm.Controller,
|
|
447
|
+
{
|
|
448
|
+
name: field.name,
|
|
449
|
+
control,
|
|
450
|
+
defaultValue,
|
|
451
|
+
rules,
|
|
452
|
+
render: ({ field: formField }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
453
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
454
|
+
material.FormLabel,
|
|
455
|
+
{
|
|
456
|
+
required: field.required,
|
|
457
|
+
error: !!errors[field.name],
|
|
458
|
+
children: field.label
|
|
459
|
+
}
|
|
460
|
+
),
|
|
461
|
+
/* @__PURE__ */ jsxRuntime.jsxs(LocalizationProvider.LocalizationProvider, { dateAdapter: AdapterDayjs.AdapterDayjs, children: [
|
|
462
|
+
mode === "date" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
463
|
+
DatePicker.DatePicker,
|
|
464
|
+
{
|
|
465
|
+
format: "DD-MM-YYYY",
|
|
466
|
+
value: formField.value ? dayjs__default.default(formField.value) : null,
|
|
467
|
+
onChange: (date) => formField.onChange(date?.toISOString() ?? null),
|
|
468
|
+
slotProps: { textField: textFieldSlotProps(field, errors) }
|
|
469
|
+
}
|
|
470
|
+
),
|
|
471
|
+
mode === "datetime" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
472
|
+
DateTimePicker.DateTimePicker,
|
|
473
|
+
{
|
|
474
|
+
format: "DD-MM-YYYY hh:mm A",
|
|
475
|
+
value: formField.value ? dayjs__default.default(formField.value) : null,
|
|
476
|
+
onChange: (date) => formField.onChange(date?.toISOString() ?? null),
|
|
477
|
+
slotProps: { textField: textFieldSlotProps(field, errors) }
|
|
478
|
+
}
|
|
479
|
+
),
|
|
480
|
+
mode === "time" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
481
|
+
TimePicker.TimePicker,
|
|
482
|
+
{
|
|
483
|
+
format: "hh:mm A",
|
|
484
|
+
value: formField.value ? dayjs__default.default(formField.value) : null,
|
|
485
|
+
onChange: (date) => formField.onChange(date?.toISOString() ?? null),
|
|
486
|
+
slotProps: { textField: textFieldSlotProps(field, errors) }
|
|
487
|
+
}
|
|
488
|
+
)
|
|
489
|
+
] })
|
|
490
|
+
] })
|
|
491
|
+
},
|
|
492
|
+
field.name
|
|
493
|
+
);
|
|
494
|
+
};
|
|
495
|
+
var useFormColors = (customColors) => {
|
|
496
|
+
const theme = material.useTheme();
|
|
497
|
+
return {
|
|
498
|
+
primary: customColors?.primary || theme.palette.primary.main,
|
|
499
|
+
secondary: customColors?.secondary || theme.palette.secondary.main,
|
|
500
|
+
error: customColors?.error || theme.palette.error.main,
|
|
501
|
+
success: customColors?.success || theme.palette.success.main,
|
|
502
|
+
warning: customColors?.warning || theme.palette.warning.main,
|
|
503
|
+
info: customColors?.info || theme.palette.info.main,
|
|
504
|
+
textPrimary: customColors?.textPrimary || theme.palette.text.primary,
|
|
505
|
+
textSecondary: customColors?.textSecondary || theme.palette.text.secondary,
|
|
506
|
+
divider: customColors?.divider || theme.palette.divider,
|
|
507
|
+
background: customColors?.background || theme.palette.background.default,
|
|
508
|
+
backgroundPaper: customColors?.backgroundPaper || theme.palette.background.paper
|
|
509
|
+
};
|
|
510
|
+
};
|
|
511
|
+
var defaultFileUploadService = {
|
|
512
|
+
uploadFiles: async () => {
|
|
513
|
+
throw new Error("File upload service not provided. Please provide a fileUpload service in FormServices.");
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
var defaultFormReferenceService = {
|
|
517
|
+
fetchOptions: async () => {
|
|
518
|
+
throw new Error("Form reference service not provided. Please provide a formReference service in FormServices.");
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
var defaultApiReferenceService = {
|
|
522
|
+
fetchOptions: async () => {
|
|
523
|
+
throw new Error("API reference service not provided. Please provide an apiReference service in FormServices.");
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
// src/utils/ckeditorLoader.ts
|
|
528
|
+
var loadCKEditor = (scriptPath = "/lib/ckeditor/ckeditor.js") => {
|
|
529
|
+
return new Promise((resolve, reject) => {
|
|
530
|
+
if (window.ClassicEditor) {
|
|
531
|
+
resolve();
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const existingScript = document.querySelector(`script[src="${scriptPath}"]`);
|
|
535
|
+
if (existingScript) {
|
|
536
|
+
existingScript.addEventListener("load", () => {
|
|
537
|
+
if (window.ClassicEditor) {
|
|
538
|
+
resolve();
|
|
539
|
+
} else {
|
|
540
|
+
reject(new Error("CKEditor script loaded but ClassicEditor not found on window"));
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
existingScript.addEventListener("error", () => {
|
|
544
|
+
reject(new Error("Failed to load CKEditor script"));
|
|
545
|
+
});
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const script = document.createElement("script");
|
|
549
|
+
script.src = scriptPath;
|
|
550
|
+
script.async = true;
|
|
551
|
+
script.onload = () => {
|
|
552
|
+
const checkInterval = setInterval(() => {
|
|
553
|
+
if (window.ClassicEditor) {
|
|
554
|
+
clearInterval(checkInterval);
|
|
555
|
+
resolve();
|
|
556
|
+
}
|
|
557
|
+
}, 100);
|
|
558
|
+
setTimeout(() => {
|
|
559
|
+
clearInterval(checkInterval);
|
|
560
|
+
if (!window.ClassicEditor) {
|
|
561
|
+
reject(
|
|
562
|
+
new Error(
|
|
563
|
+
"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)."
|
|
564
|
+
)
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
}, 1e4);
|
|
568
|
+
};
|
|
569
|
+
script.onerror = () => {
|
|
570
|
+
reject(
|
|
571
|
+
new Error(
|
|
572
|
+
`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.`
|
|
573
|
+
)
|
|
574
|
+
);
|
|
575
|
+
};
|
|
576
|
+
document.head.appendChild(script);
|
|
577
|
+
});
|
|
578
|
+
};
|
|
579
|
+
var isCKEditorAvailable = () => {
|
|
580
|
+
return typeof window !== "undefined" && typeof window.ClassicEditor !== "undefined";
|
|
581
|
+
};
|
|
582
|
+
var waitForCKEditor = (timeout = 1e4) => {
|
|
583
|
+
return new Promise((resolve, reject) => {
|
|
584
|
+
if (isCKEditorAvailable()) {
|
|
585
|
+
resolve();
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const startTime = Date.now();
|
|
589
|
+
const checkInterval = setInterval(() => {
|
|
590
|
+
if (isCKEditorAvailable()) {
|
|
591
|
+
clearInterval(checkInterval);
|
|
592
|
+
resolve();
|
|
593
|
+
} else if (Date.now() - startTime > timeout) {
|
|
594
|
+
clearInterval(checkInterval);
|
|
595
|
+
reject(new Error("CKEditor not available after timeout"));
|
|
596
|
+
}
|
|
597
|
+
}, 100);
|
|
598
|
+
});
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
// src/hooks/useCKEditor.ts
|
|
602
|
+
var useCKEditor = (options = {}) => {
|
|
603
|
+
const {
|
|
604
|
+
scriptPath = "/lib/ckeditor/ckeditor.js",
|
|
605
|
+
autoLoad = true,
|
|
606
|
+
timeout = 1e4
|
|
607
|
+
} = options;
|
|
608
|
+
const [isReady, setIsReady] = react.useState(false);
|
|
609
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
610
|
+
const [error, setError] = react.useState(null);
|
|
611
|
+
const load = async () => {
|
|
612
|
+
if (isCKEditorAvailable()) {
|
|
613
|
+
setIsReady(true);
|
|
614
|
+
setIsLoading(false);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
setIsLoading(true);
|
|
618
|
+
setError(null);
|
|
619
|
+
try {
|
|
620
|
+
await loadCKEditor(scriptPath);
|
|
621
|
+
setIsReady(true);
|
|
622
|
+
} catch (err) {
|
|
623
|
+
const error2 = err instanceof Error ? err : new Error("Failed to load CKEditor");
|
|
624
|
+
setError(error2);
|
|
625
|
+
setIsReady(false);
|
|
626
|
+
} finally {
|
|
627
|
+
setIsLoading(false);
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
react.useEffect(() => {
|
|
631
|
+
if (isCKEditorAvailable()) {
|
|
632
|
+
setIsReady(true);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
if (autoLoad) {
|
|
636
|
+
load();
|
|
637
|
+
} else {
|
|
638
|
+
waitForCKEditor(timeout).then(() => setIsReady(true)).catch((err) => setError(err instanceof Error ? err : new Error("CKEditor not available")));
|
|
639
|
+
}
|
|
640
|
+
}, [scriptPath, autoLoad, timeout]);
|
|
641
|
+
return {
|
|
642
|
+
isReady,
|
|
643
|
+
isLoading,
|
|
644
|
+
error,
|
|
645
|
+
load
|
|
646
|
+
};
|
|
647
|
+
};
|
|
648
|
+
var CKEditorField = ({ field, control, defaultValue, rules, errors, setValue, formSchema, services, colors }) => {
|
|
649
|
+
const theme = material.useTheme();
|
|
650
|
+
const formColors = useFormColors(colors);
|
|
651
|
+
const editorContainerRef = react.useRef(null);
|
|
652
|
+
const editorInstanceRef = react.useRef(null);
|
|
653
|
+
const isUserTypingRef = react.useRef(false);
|
|
654
|
+
const fileUploadService = services?.fileUpload || defaultFileUploadService;
|
|
655
|
+
const fileBaseUrl = services?.fileBaseUrl || "";
|
|
656
|
+
const licenseKey = services?.ckEditorLicenseKey || "GPL";
|
|
657
|
+
const ckEditorScriptPath = services?.ckEditorScriptPath || "/lib/ckeditor/ckeditor.js";
|
|
658
|
+
const { isReady: isCKEditorReady, isLoading: isCKEditorLoading, error: ckEditorError } = useCKEditor({
|
|
659
|
+
scriptPath: ckEditorScriptPath,
|
|
660
|
+
autoLoad: true
|
|
661
|
+
});
|
|
662
|
+
const watchedValue = reactHookForm.useWatch({
|
|
663
|
+
control,
|
|
664
|
+
name: field.name,
|
|
665
|
+
defaultValue: defaultValue || ""
|
|
666
|
+
});
|
|
667
|
+
react.useEffect(() => {
|
|
668
|
+
if (editorInstanceRef.current && isCKEditorReady && !isUserTypingRef.current) {
|
|
669
|
+
const currentEditorData = editorInstanceRef.current.getData();
|
|
670
|
+
const formValue = watchedValue || "";
|
|
671
|
+
if (currentEditorData !== formValue) {
|
|
672
|
+
editorInstanceRef.current.setData(formValue);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
isUserTypingRef.current = false;
|
|
676
|
+
}, [watchedValue, isCKEditorReady]);
|
|
677
|
+
const createCustomUploadAdapter = react.useCallback((loader) => {
|
|
678
|
+
return {
|
|
679
|
+
upload: async () => {
|
|
680
|
+
if (!formSchema?.name) {
|
|
681
|
+
throw new Error("Form schema name is required for image uploads");
|
|
682
|
+
}
|
|
683
|
+
try {
|
|
684
|
+
const file = await loader.file;
|
|
685
|
+
if (!file) {
|
|
686
|
+
throw new Error("No file provided");
|
|
687
|
+
}
|
|
688
|
+
if (!file.type.startsWith("image/")) {
|
|
689
|
+
throw new Error("Only image files are allowed");
|
|
690
|
+
}
|
|
691
|
+
const uploadedFiles = await fileUploadService.uploadFiles(
|
|
692
|
+
formSchema.name,
|
|
693
|
+
field.name,
|
|
694
|
+
[file]
|
|
695
|
+
);
|
|
696
|
+
if (uploadedFiles && uploadedFiles.length > 0 && uploadedFiles[0].fileUrl) {
|
|
697
|
+
const fileUrl = uploadedFiles[0].fileUrl;
|
|
698
|
+
const isFullUrl = fileUrl.startsWith("http://") || fileUrl.startsWith("https://");
|
|
699
|
+
const url = isFullUrl ? fileUrl : fileBaseUrl + fileUrl;
|
|
700
|
+
return {
|
|
701
|
+
default: url
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
throw new Error("Upload failed: No file URL returned");
|
|
705
|
+
} catch (error) {
|
|
706
|
+
console.error("Upload error:", error);
|
|
707
|
+
const errorMessage = error.response?.data?.message || error.message || "Upload failed";
|
|
708
|
+
throw new Error(errorMessage);
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
abort: () => {
|
|
712
|
+
console.log("Upload aborted");
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
}, [formSchema?.name, field.name, fileUploadService, fileBaseUrl]);
|
|
716
|
+
if (isCKEditorLoading) {
|
|
717
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
|
|
718
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.FormLabel, { required: field.required, error: !!errors[field.name], children: field.label }),
|
|
719
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { p: 2, textAlign: "center", color: formColors.textSecondary }, children: "Loading editor..." })
|
|
720
|
+
] });
|
|
721
|
+
}
|
|
722
|
+
if (ckEditorError || !isCKEditorReady) {
|
|
723
|
+
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.`;
|
|
724
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
|
|
725
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.FormLabel, { required: field.required, error: !!errors[field.name], children: field.label }),
|
|
726
|
+
/* @__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 }) })
|
|
727
|
+
] });
|
|
728
|
+
}
|
|
729
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
730
|
+
reactHookForm.Controller,
|
|
731
|
+
{
|
|
732
|
+
name: field.name,
|
|
733
|
+
control,
|
|
734
|
+
defaultValue: defaultValue || "",
|
|
735
|
+
rules,
|
|
736
|
+
render: ({ field: formField }) => /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
|
|
737
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
738
|
+
material.FormLabel,
|
|
739
|
+
{
|
|
740
|
+
required: field.required,
|
|
741
|
+
error: !!errors[field.name],
|
|
742
|
+
children: field.label
|
|
743
|
+
}
|
|
744
|
+
),
|
|
745
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
746
|
+
material.Box,
|
|
747
|
+
{
|
|
748
|
+
ref: editorContainerRef,
|
|
749
|
+
sx: {
|
|
750
|
+
"& .ck-editor": {
|
|
751
|
+
borderRadius: "4px",
|
|
752
|
+
"& .ck-toolbar": {
|
|
753
|
+
width: "100%",
|
|
754
|
+
maxWidth: "100%",
|
|
755
|
+
overflow: "visible",
|
|
756
|
+
display: "flex",
|
|
757
|
+
flexWrap: "wrap",
|
|
758
|
+
"& .ck-toolbar__items": {
|
|
759
|
+
display: "flex",
|
|
760
|
+
flexWrap: "wrap !important",
|
|
761
|
+
width: "100%",
|
|
762
|
+
maxWidth: "100%",
|
|
763
|
+
"& > *": {
|
|
764
|
+
flexShrink: 0
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
"& .ck-editor__editable": {
|
|
769
|
+
minHeight: "100px"
|
|
770
|
+
},
|
|
771
|
+
"&:hover": {
|
|
772
|
+
borderColor: errors[field.name] ? formColors.error : theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.87)" : formColors.primary
|
|
773
|
+
},
|
|
774
|
+
"& .ck-focused": {
|
|
775
|
+
border: errors[field.name] ? `1px solid ${formColors.error} !important` : `1px solid ${theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.23)" : formColors.primary} !important`,
|
|
776
|
+
boxShadow: errors[field.name] ? `0 0 0 1px ${formColors.error}` : `0 0 0 1px ${material.alpha(formColors.primary, 0.5)} !important`
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
children: window.ClassicEditor && isCKEditorReady && /* @__PURE__ */ jsxRuntime.jsx(
|
|
781
|
+
ckeditor5React.CKEditor,
|
|
782
|
+
{
|
|
783
|
+
editor: window.ClassicEditor,
|
|
784
|
+
config: {
|
|
785
|
+
licenseKey,
|
|
786
|
+
initialData: formField.value || ""
|
|
787
|
+
},
|
|
788
|
+
data: formField.value || "",
|
|
789
|
+
onReady: (editor) => {
|
|
790
|
+
editorInstanceRef.current = editor;
|
|
791
|
+
if (formSchema?.name) {
|
|
792
|
+
try {
|
|
793
|
+
const fileRepository = editor.plugins.get("FileRepository");
|
|
794
|
+
if (fileRepository) {
|
|
795
|
+
fileRepository.createUploadAdapter = (loader) => {
|
|
796
|
+
return createCustomUploadAdapter(loader);
|
|
797
|
+
};
|
|
798
|
+
} else {
|
|
799
|
+
console.warn("FileRepository plugin not found");
|
|
800
|
+
}
|
|
801
|
+
} catch (error) {
|
|
802
|
+
console.error("Error setting up upload adapter:", error);
|
|
803
|
+
}
|
|
804
|
+
} else {
|
|
805
|
+
console.warn("Form schema name not available, upload adapter not set");
|
|
806
|
+
}
|
|
807
|
+
if (defaultValue && !formField.value) {
|
|
808
|
+
editor.setData(defaultValue);
|
|
809
|
+
setValue?.(field.name, defaultValue);
|
|
810
|
+
}
|
|
811
|
+
},
|
|
812
|
+
onChange: (_event, editor) => {
|
|
813
|
+
const data = editor.getData();
|
|
814
|
+
isUserTypingRef.current = true;
|
|
815
|
+
formField.onChange(data);
|
|
816
|
+
},
|
|
817
|
+
onBlur: () => {
|
|
818
|
+
formField.onBlur();
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
)
|
|
822
|
+
}
|
|
823
|
+
),
|
|
824
|
+
errors[field.name] && /* @__PURE__ */ jsxRuntime.jsx(material.FormHelperText, { error: true, sx: { mt: 0.5, mx: 0 }, children: errors[field.name]?.message })
|
|
825
|
+
] })
|
|
826
|
+
},
|
|
827
|
+
field.name
|
|
828
|
+
);
|
|
829
|
+
};
|
|
830
|
+
var FileField = ({
|
|
831
|
+
field,
|
|
832
|
+
control,
|
|
833
|
+
defaultValue,
|
|
834
|
+
rules,
|
|
835
|
+
errors,
|
|
836
|
+
formSchema,
|
|
837
|
+
uploadingFiles = {},
|
|
838
|
+
setUploadingFiles,
|
|
839
|
+
setError,
|
|
840
|
+
clearErrors,
|
|
841
|
+
services,
|
|
842
|
+
colors
|
|
843
|
+
}) => {
|
|
844
|
+
const theme = material.useTheme();
|
|
845
|
+
const isMobile = material.useMediaQuery(theme.breakpoints.down("md"));
|
|
846
|
+
const formColors = useFormColors(colors);
|
|
847
|
+
const acceptTypes = field.validation?.allowedFileTypes ? field.validation.allowedFileTypes.map((type) => `.${type.replace(".", "")}`).join(",") : void 0;
|
|
848
|
+
const isMultiple = field.allowMultiple || false;
|
|
849
|
+
const fileUploadService = services?.fileUpload || defaultFileUploadService;
|
|
850
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
851
|
+
reactHookForm.Controller,
|
|
852
|
+
{
|
|
853
|
+
name: field.name,
|
|
854
|
+
control,
|
|
855
|
+
defaultValue,
|
|
856
|
+
rules,
|
|
857
|
+
render: ({ field: formField }) => {
|
|
858
|
+
const isUploading = uploadingFiles[field.name] || false;
|
|
859
|
+
let files = [];
|
|
860
|
+
if (isMultiple) {
|
|
861
|
+
files = Array.isArray(formField.value) ? formField.value : [];
|
|
862
|
+
} else {
|
|
863
|
+
files = formField.value ? [formField.value] : [];
|
|
864
|
+
}
|
|
865
|
+
const hasFiles = files.length > 0;
|
|
866
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
|
|
867
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
868
|
+
material.FormLabel,
|
|
869
|
+
{
|
|
870
|
+
required: field.required,
|
|
871
|
+
error: !!errors[field.name],
|
|
872
|
+
children: field.label
|
|
873
|
+
}
|
|
874
|
+
),
|
|
875
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
876
|
+
material.Box,
|
|
877
|
+
{
|
|
878
|
+
component: "label",
|
|
879
|
+
htmlFor: `file-input-${field.name}`,
|
|
880
|
+
sx: {
|
|
881
|
+
display: "flex",
|
|
882
|
+
flexDirection: "column",
|
|
883
|
+
alignItems: "center",
|
|
884
|
+
justifyContent: "center",
|
|
885
|
+
border: "1px dashed",
|
|
886
|
+
borderColor: errors[field.name] ? formColors.error : isUploading ? formColors.primary : hasFiles ? formColors.primary : formColors.divider,
|
|
887
|
+
p: 1,
|
|
888
|
+
cursor: isUploading ? "wait" : "pointer",
|
|
889
|
+
transition: "all 0.2s ease-in-out",
|
|
890
|
+
backgroundColor: isUploading || hasFiles ? "action.hover" : formColors.backgroundPaper,
|
|
891
|
+
opacity: isUploading ? 0.7 : 1,
|
|
892
|
+
pointerEvents: isUploading ? "none" : "auto",
|
|
893
|
+
"&:hover": {
|
|
894
|
+
borderColor: isUploading ? formColors.primary : formColors.primary,
|
|
895
|
+
backgroundColor: "action.hover"
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
children: [
|
|
899
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
900
|
+
"input",
|
|
901
|
+
{
|
|
902
|
+
id: `file-input-${field.name}`,
|
|
903
|
+
type: "file",
|
|
904
|
+
hidden: true,
|
|
905
|
+
multiple: isMultiple,
|
|
906
|
+
accept: acceptTypes,
|
|
907
|
+
onChange: async (e) => {
|
|
908
|
+
const fileList = e.target.files;
|
|
909
|
+
if (!fileList || fileList.length === 0) return;
|
|
910
|
+
const newFiles = Array.from(fileList);
|
|
911
|
+
const isUploading2 = uploadingFiles[field.name] || false;
|
|
912
|
+
if (isUploading2) {
|
|
913
|
+
e.target.value = "";
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
for (const file of newFiles) {
|
|
917
|
+
const validationResult = validateFile(file, field);
|
|
918
|
+
if (validationResult !== true) {
|
|
919
|
+
setError?.(field.name, {
|
|
920
|
+
type: "manual",
|
|
921
|
+
message: validationResult
|
|
922
|
+
});
|
|
923
|
+
e.target.value = "";
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
try {
|
|
928
|
+
setUploadingFiles?.((prev) => ({ ...prev, [field.name]: true }));
|
|
929
|
+
clearErrors?.(field.name);
|
|
930
|
+
if (!formSchema?.name) {
|
|
931
|
+
throw new Error("Form schema name is required for file uploads");
|
|
932
|
+
}
|
|
933
|
+
const uploadedFiles = await fileUploadService.uploadFiles(
|
|
934
|
+
formSchema.name,
|
|
935
|
+
field.name,
|
|
936
|
+
newFiles
|
|
937
|
+
);
|
|
938
|
+
if (isMultiple) {
|
|
939
|
+
const currentValue = formField.value;
|
|
940
|
+
const existingFiles = Array.isArray(currentValue) ? currentValue.filter((item) => item && typeof item === "object" && "fileName" in item) : [];
|
|
941
|
+
const allUploadedFiles = [...existingFiles, ...uploadedFiles];
|
|
942
|
+
formField.onChange(allUploadedFiles);
|
|
943
|
+
} else {
|
|
944
|
+
formField.onChange(uploadedFiles[0] || null);
|
|
945
|
+
}
|
|
946
|
+
} catch (error) {
|
|
947
|
+
console.error(`Failed to upload files for field ${field.name}:`, error);
|
|
948
|
+
const errorMessage = error.response?.data?.message || error.message || "Failed to upload files";
|
|
949
|
+
setError?.(field.name, {
|
|
950
|
+
type: "manual",
|
|
951
|
+
message: `Failed to upload files: ${errorMessage}`
|
|
952
|
+
});
|
|
953
|
+
} finally {
|
|
954
|
+
setUploadingFiles?.((prev) => ({ ...prev, [field.name]: false }));
|
|
955
|
+
e.target.value = "";
|
|
956
|
+
}
|
|
957
|
+
},
|
|
958
|
+
disabled: uploadingFiles[field.name] || false
|
|
959
|
+
}
|
|
960
|
+
),
|
|
961
|
+
isUploading ? /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { display: "flex", flexDirection: "column", alignItems: "center", gap: 1, py: 3, width: "100%" }, children: [
|
|
962
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.CircularProgress, { size: 40 }),
|
|
963
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", sx: { fontWeight: 500, color: formColors.primary }, children: "Uploading files..." }),
|
|
964
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "caption", sx: { color: formColors.textSecondary }, children: "Please wait while files are being uploaded" })
|
|
965
|
+
] }) : !hasFiles ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
966
|
+
/* @__PURE__ */ jsxRuntime.jsx(CloudUploadIcon__default.default, { sx: { fontSize: 40, color: formColors.textSecondary, mb: 1 } }),
|
|
967
|
+
/* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "caption", sx: { color: formColors.textSecondary }, children: [
|
|
968
|
+
"Click to upload or drag and drop",
|
|
969
|
+
isMultiple && " (multiple files allowed)"
|
|
970
|
+
] }),
|
|
971
|
+
field.validation?.allowedFileTypes && field.validation.allowedFileTypes.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "caption", sx: { mt: 0.5, color: formColors.textSecondary }, children: [
|
|
972
|
+
"Allowed: ",
|
|
973
|
+
field.validation.allowedFileTypes.join(", ")
|
|
974
|
+
] }),
|
|
975
|
+
field.validation?.maxFileSize && /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "caption", sx: { mt: 0.5, color: formColors.textSecondary }, children: [
|
|
976
|
+
"Max size: ",
|
|
977
|
+
formatFileSize(field.validation.maxFileSize),
|
|
978
|
+
" per file"
|
|
979
|
+
] })
|
|
980
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { width: "100%" }, children: [
|
|
981
|
+
isMultiple && /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "body2", sx: { fontWeight: 500, mb: 1.5 }, children: [
|
|
982
|
+
files.length,
|
|
983
|
+
" file",
|
|
984
|
+
files.length !== 1 ? "s" : "",
|
|
985
|
+
" uploaded"
|
|
986
|
+
] }),
|
|
987
|
+
files.length === 1 && !isMultiple ? /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { display: "flex", alignItems: "center", gap: 2, width: "100%" }, children: [
|
|
988
|
+
/* @__PURE__ */ jsxRuntime.jsx(InsertDriveFileIcon__default.default, { sx: { fontSize: 40, color: formColors.primary } }),
|
|
989
|
+
/* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { flexGrow: 1, minWidth: 0 }, children: [
|
|
990
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", sx: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", maxWidth: isMobile ? "200px" : "300px" }, children: files[0] instanceof File ? files[0].name : files[0].originalName || files[0].fileName }),
|
|
991
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "caption", sx: { color: formColors.textSecondary }, children: formatFileSize(files[0].size || 0) })
|
|
992
|
+
] }),
|
|
993
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.Tooltip, { title: "Remove file", placement: "bottom", arrow: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
994
|
+
material.IconButton,
|
|
995
|
+
{
|
|
996
|
+
size: "small",
|
|
997
|
+
onClick: (e) => {
|
|
998
|
+
e.preventDefault();
|
|
999
|
+
e.stopPropagation();
|
|
1000
|
+
formField.onChange(null);
|
|
1001
|
+
const fileInput = document.getElementById(`file-input-${field.name}`);
|
|
1002
|
+
if (fileInput) {
|
|
1003
|
+
fileInput.value = "";
|
|
1004
|
+
}
|
|
1005
|
+
},
|
|
1006
|
+
sx: { color: formColors.error },
|
|
1007
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon__default.default, { fontSize: "small" })
|
|
1008
|
+
}
|
|
1009
|
+
) })
|
|
1010
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { display: "flex", flexDirection: "column", gap: 1, width: "100%" }, children: files.map((file, index) => {
|
|
1011
|
+
const fileName = file instanceof File ? file.name : file.originalName || file.fileName;
|
|
1012
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1013
|
+
material.Box,
|
|
1014
|
+
{
|
|
1015
|
+
sx: {
|
|
1016
|
+
display: "flex",
|
|
1017
|
+
alignItems: "center",
|
|
1018
|
+
gap: 1.5,
|
|
1019
|
+
p: 1,
|
|
1020
|
+
borderRadius: 1,
|
|
1021
|
+
backgroundColor: formColors.background,
|
|
1022
|
+
border: "1px solid",
|
|
1023
|
+
borderColor: formColors.divider
|
|
1024
|
+
},
|
|
1025
|
+
children: [
|
|
1026
|
+
/* @__PURE__ */ jsxRuntime.jsx(InsertDriveFileIcon__default.default, { sx: { fontSize: 32, color: formColors.primary } }),
|
|
1027
|
+
/* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { flexGrow: 1, minWidth: 0 }, children: [
|
|
1028
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", sx: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: fileName }),
|
|
1029
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "caption", sx: { color: formColors.textSecondary }, children: formatFileSize(file.size || 0) })
|
|
1030
|
+
] }),
|
|
1031
|
+
/* @__PURE__ */ jsxRuntime.jsx(material.Tooltip, { title: "Remove file", placement: "bottom", arrow: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1032
|
+
material.IconButton,
|
|
1033
|
+
{
|
|
1034
|
+
size: "small",
|
|
1035
|
+
onClick: (e) => {
|
|
1036
|
+
e.preventDefault();
|
|
1037
|
+
e.stopPropagation();
|
|
1038
|
+
if (isMultiple) {
|
|
1039
|
+
const updatedFiles = files.filter((_, i) => i !== index);
|
|
1040
|
+
formField.onChange(updatedFiles.length > 0 ? updatedFiles : []);
|
|
1041
|
+
} else {
|
|
1042
|
+
formField.onChange(null);
|
|
1043
|
+
}
|
|
1044
|
+
},
|
|
1045
|
+
sx: { color: formColors.error },
|
|
1046
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon__default.default, { fontSize: "small" })
|
|
1047
|
+
}
|
|
1048
|
+
) })
|
|
1049
|
+
]
|
|
1050
|
+
},
|
|
1051
|
+
`${fileName}-${index}`
|
|
1052
|
+
);
|
|
1053
|
+
}) })
|
|
1054
|
+
] })
|
|
1055
|
+
]
|
|
1056
|
+
}
|
|
1057
|
+
),
|
|
1058
|
+
errors[field.name] && /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "caption", sx: { mt: 1, display: "block", color: formColors.error }, children: errors[field.name]?.message })
|
|
1059
|
+
] });
|
|
1060
|
+
}
|
|
1061
|
+
},
|
|
1062
|
+
field.name
|
|
1063
|
+
);
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
// src/utils/formHelpers.ts
|
|
1067
|
+
var getDefaultValue = (field) => {
|
|
1068
|
+
switch (field.type) {
|
|
1069
|
+
case "checkbox":
|
|
1070
|
+
case "toggle":
|
|
1071
|
+
return false;
|
|
1072
|
+
case "datepicker":
|
|
1073
|
+
return null;
|
|
1074
|
+
case "file":
|
|
1075
|
+
return field.allowMultiple ? [] : null;
|
|
1076
|
+
case "select":
|
|
1077
|
+
case "formReference":
|
|
1078
|
+
case "apiReference":
|
|
1079
|
+
return field.allowMultiple ? [] : "";
|
|
1080
|
+
case "text":
|
|
1081
|
+
case "email":
|
|
1082
|
+
case "number":
|
|
1083
|
+
case "radio":
|
|
1084
|
+
case "ckeditor":
|
|
1085
|
+
default:
|
|
1086
|
+
return "";
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
var FormReferenceField = ({ field, control, defaultValue, rules, errors, services }) => {
|
|
1090
|
+
const isMultiple = field.allowMultiple || false;
|
|
1091
|
+
const [options, setOptions] = react.useState([]);
|
|
1092
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
1093
|
+
const formReferenceService = services?.formReference || defaultFormReferenceService;
|
|
1094
|
+
const fetchOptions = react.useCallback(() => {
|
|
1095
|
+
if (!field.referenceFormName || !field.referenceFieldName) return;
|
|
1096
|
+
setIsLoading(true);
|
|
1097
|
+
formReferenceService.fetchOptions(field.referenceFormName, field.referenceFieldName).then((opts) => {
|
|
1098
|
+
setOptions(opts);
|
|
1099
|
+
}).catch((error) => {
|
|
1100
|
+
console.error("Failed to fetch form reference options:", error);
|
|
1101
|
+
setOptions([]);
|
|
1102
|
+
}).finally(() => {
|
|
1103
|
+
setIsLoading(false);
|
|
1104
|
+
});
|
|
1105
|
+
}, [field.referenceFormName, field.referenceFieldName, formReferenceService]);
|
|
1106
|
+
react.useEffect(() => {
|
|
1107
|
+
fetchOptions();
|
|
1108
|
+
}, [fetchOptions]);
|
|
1109
|
+
const selectOptions = react.useMemo(() => {
|
|
1110
|
+
return options.map((opt) => ({
|
|
1111
|
+
value: String(opt.value),
|
|
1112
|
+
label: opt.label
|
|
1113
|
+
}));
|
|
1114
|
+
}, [options]);
|
|
1115
|
+
const fieldDefaultValue = defaultValue ?? getDefaultValue(field);
|
|
1116
|
+
const fieldRules = rules ?? buildFieldRules(field);
|
|
1117
|
+
const isDisabled = !field.referenceFormName || !field.referenceFieldName;
|
|
1118
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1119
|
+
reactHookForm.Controller,
|
|
1120
|
+
{
|
|
1121
|
+
name: field.name,
|
|
1122
|
+
control,
|
|
1123
|
+
defaultValue: fieldDefaultValue,
|
|
1124
|
+
rules: fieldRules,
|
|
1125
|
+
render: ({ field: formField }) => {
|
|
1126
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1127
|
+
SimpleSelect,
|
|
1128
|
+
{
|
|
1129
|
+
label: field.label,
|
|
1130
|
+
value: formField.value,
|
|
1131
|
+
onChange: (value) => {
|
|
1132
|
+
formField.onChange(value);
|
|
1133
|
+
},
|
|
1134
|
+
options: selectOptions,
|
|
1135
|
+
placeholder: field.placeholder || "Search and select...",
|
|
1136
|
+
helperText: errors[field.name]?.message,
|
|
1137
|
+
fullWidth: true,
|
|
1138
|
+
size: "small",
|
|
1139
|
+
required: field.required,
|
|
1140
|
+
error: !!errors[field.name],
|
|
1141
|
+
disabled: isDisabled || isLoading,
|
|
1142
|
+
multiple: isMultiple,
|
|
1143
|
+
isLoading,
|
|
1144
|
+
refreshable: true,
|
|
1145
|
+
onRefresh: fetchOptions
|
|
1146
|
+
}
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
},
|
|
1150
|
+
field.name
|
|
1151
|
+
);
|
|
1152
|
+
};
|
|
1153
|
+
var ApiReferenceField = ({ field, control, defaultValue, rules, errors, services }) => {
|
|
1154
|
+
const isMultiple = field.allowMultiple || false;
|
|
1155
|
+
const [options, setOptions] = react.useState([]);
|
|
1156
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
1157
|
+
const apiReferenceService = services?.apiReference || defaultApiReferenceService;
|
|
1158
|
+
const fetchOptions = react.useCallback(() => {
|
|
1159
|
+
if (!field.apiEndpoint || !field.apiLabelField) return;
|
|
1160
|
+
setIsLoading(true);
|
|
1161
|
+
apiReferenceService.fetchOptions(field.apiEndpoint, field.apiLabelField, field.apiValueField || "_id").then((opts) => {
|
|
1162
|
+
setOptions(opts);
|
|
1163
|
+
}).catch((error) => {
|
|
1164
|
+
console.error("Failed to fetch API reference options:", error);
|
|
1165
|
+
setOptions([]);
|
|
1166
|
+
}).finally(() => {
|
|
1167
|
+
setIsLoading(false);
|
|
1168
|
+
});
|
|
1169
|
+
}, [field.apiEndpoint, field.apiLabelField, field.apiValueField, apiReferenceService]);
|
|
1170
|
+
react.useEffect(() => {
|
|
1171
|
+
fetchOptions();
|
|
1172
|
+
}, [fetchOptions]);
|
|
1173
|
+
const selectOptions = react.useMemo(() => {
|
|
1174
|
+
return options.map((opt) => ({
|
|
1175
|
+
value: String(opt.value),
|
|
1176
|
+
label: opt.label
|
|
1177
|
+
}));
|
|
1178
|
+
}, [options]);
|
|
1179
|
+
const fieldDefaultValue = defaultValue ?? getDefaultValue(field);
|
|
1180
|
+
const fieldRules = rules ?? buildFieldRules(field);
|
|
1181
|
+
const isDisabled = !field.apiEndpoint || !field.apiLabelField;
|
|
1182
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1183
|
+
reactHookForm.Controller,
|
|
1184
|
+
{
|
|
1185
|
+
name: field.name,
|
|
1186
|
+
control,
|
|
1187
|
+
defaultValue: fieldDefaultValue,
|
|
1188
|
+
rules: fieldRules,
|
|
1189
|
+
render: ({ field: formField }) => {
|
|
1190
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1191
|
+
SimpleSelect,
|
|
1192
|
+
{
|
|
1193
|
+
label: field.label,
|
|
1194
|
+
value: formField.value,
|
|
1195
|
+
onChange: (value) => {
|
|
1196
|
+
formField.onChange(value);
|
|
1197
|
+
},
|
|
1198
|
+
options: selectOptions,
|
|
1199
|
+
placeholder: field.placeholder || "Search and select...",
|
|
1200
|
+
helperText: errors[field.name]?.message,
|
|
1201
|
+
fullWidth: true,
|
|
1202
|
+
size: "small",
|
|
1203
|
+
required: field.required,
|
|
1204
|
+
error: !!errors[field.name],
|
|
1205
|
+
disabled: isDisabled || isLoading,
|
|
1206
|
+
multiple: isMultiple,
|
|
1207
|
+
isLoading,
|
|
1208
|
+
refreshable: true,
|
|
1209
|
+
onRefresh: fetchOptions
|
|
1210
|
+
}
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
},
|
|
1214
|
+
field.name
|
|
1215
|
+
);
|
|
1216
|
+
};
|
|
1217
|
+
|
|
1218
|
+
exports.ApiReferenceField = ApiReferenceField;
|
|
1219
|
+
exports.CKEditorField = CKEditorField;
|
|
1220
|
+
exports.CheckboxField = CheckboxField;
|
|
1221
|
+
exports.ColorField = ColorField;
|
|
1222
|
+
exports.DateTimePickerField = DateTimePickerField;
|
|
1223
|
+
exports.FileField = FileField;
|
|
1224
|
+
exports.FormReferenceField = FormReferenceField;
|
|
1225
|
+
exports.RadioField = RadioField;
|
|
1226
|
+
exports.SelectField = SelectField;
|
|
1227
|
+
exports.TextField = TextField;
|
|
1228
|
+
exports.ToggleField = ToggleField;
|
|
1229
|
+
//# sourceMappingURL=fields.js.map
|
|
1230
|
+
//# sourceMappingURL=fields.js.map
|