@fogpipe/forma-react 0.12.0 → 0.14.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 +140 -61
- package/dist/index.d.ts +90 -1
- package/dist/index.js +303 -51
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/ErrorBoundary.tsx +14 -7
- package/src/FieldRenderer.tsx +3 -1
- package/src/FormRenderer.tsx +3 -1
- package/src/__tests__/FieldRenderer.test.tsx +128 -1
- package/src/__tests__/FormRenderer.test.tsx +54 -0
- package/src/__tests__/canProceed.test.ts +141 -100
- package/src/__tests__/diabetes-trial-flow.test.ts +235 -66
- package/src/__tests__/events.test.ts +752 -0
- package/src/__tests__/null-handling.test.ts +27 -8
- package/src/__tests__/optionVisibility.test.tsx +199 -58
- package/src/__tests__/test-utils.tsx +26 -7
- package/src/__tests__/useForma.test.ts +244 -73
- package/src/context.ts +3 -1
- package/src/events.ts +186 -0
- package/src/index.ts +11 -1
- package/src/types.ts +65 -14
- package/src/useForma.ts +292 -53
package/dist/index.js
CHANGED
|
@@ -8,6 +8,74 @@ import {
|
|
|
8
8
|
useState
|
|
9
9
|
} from "react";
|
|
10
10
|
import { isAdornableField } from "@fogpipe/forma-core";
|
|
11
|
+
|
|
12
|
+
// src/events.ts
|
|
13
|
+
var FormaEventEmitter = class {
|
|
14
|
+
listeners = /* @__PURE__ */ new Map();
|
|
15
|
+
/**
|
|
16
|
+
* Register a listener for an event. Returns an unsubscribe function.
|
|
17
|
+
*/
|
|
18
|
+
on(event, listener) {
|
|
19
|
+
if (!this.listeners.has(event)) {
|
|
20
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
21
|
+
}
|
|
22
|
+
this.listeners.get(event).add(listener);
|
|
23
|
+
return () => {
|
|
24
|
+
const set = this.listeners.get(event);
|
|
25
|
+
if (set) {
|
|
26
|
+
set.delete(listener);
|
|
27
|
+
if (set.size === 0) {
|
|
28
|
+
this.listeners.delete(event);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Fire an event synchronously. Listener errors are caught and logged
|
|
35
|
+
* to prevent one listener from breaking others.
|
|
36
|
+
*/
|
|
37
|
+
fire(event, payload) {
|
|
38
|
+
const set = this.listeners.get(event);
|
|
39
|
+
if (!set || set.size === 0) return;
|
|
40
|
+
for (const listener of set) {
|
|
41
|
+
try {
|
|
42
|
+
listener(payload);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(`[forma] Error in "${event}" event listener:`, error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fire an event and await all async listeners sequentially.
|
|
50
|
+
* Used for preSubmit where handlers can be async.
|
|
51
|
+
*/
|
|
52
|
+
async fireAsync(event, payload) {
|
|
53
|
+
const set = this.listeners.get(event);
|
|
54
|
+
if (!set || set.size === 0) return;
|
|
55
|
+
for (const listener of set) {
|
|
56
|
+
try {
|
|
57
|
+
await listener(payload);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(`[forma] Error in "${event}" event listener:`, error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Check if any listeners are registered for an event.
|
|
65
|
+
*/
|
|
66
|
+
hasListeners(event) {
|
|
67
|
+
const set = this.listeners.get(event);
|
|
68
|
+
return set !== void 0 && set.size > 0;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Remove all listeners. Called on cleanup.
|
|
72
|
+
*/
|
|
73
|
+
clear() {
|
|
74
|
+
this.listeners.clear();
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/useForma.ts
|
|
11
79
|
import {
|
|
12
80
|
getVisibility,
|
|
13
81
|
getRequired,
|
|
@@ -72,6 +140,15 @@ function getDefaultBooleanValues(spec) {
|
|
|
72
140
|
}
|
|
73
141
|
return defaults;
|
|
74
142
|
}
|
|
143
|
+
function getFieldDefaults(spec) {
|
|
144
|
+
const defaults = {};
|
|
145
|
+
for (const [fieldPath, fieldDef] of Object.entries(spec.fields)) {
|
|
146
|
+
if (fieldDef.defaultValue !== void 0) {
|
|
147
|
+
defaults[fieldPath] = fieldDef.defaultValue;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return defaults;
|
|
151
|
+
}
|
|
75
152
|
function useForma(options) {
|
|
76
153
|
const {
|
|
77
154
|
spec: inputSpec,
|
|
@@ -80,7 +157,8 @@ function useForma(options) {
|
|
|
80
157
|
onChange,
|
|
81
158
|
validateOn = "blur",
|
|
82
159
|
referenceData,
|
|
83
|
-
validationDebounceMs = 0
|
|
160
|
+
validationDebounceMs = 0,
|
|
161
|
+
on: onEvents
|
|
84
162
|
} = options;
|
|
85
163
|
const spec = useMemo(() => {
|
|
86
164
|
if (!referenceData) return inputSpec;
|
|
@@ -93,8 +171,11 @@ function useForma(options) {
|
|
|
93
171
|
};
|
|
94
172
|
}, [inputSpec, referenceData]);
|
|
95
173
|
const [state, dispatch] = useReducer(formReducer, {
|
|
96
|
-
data: {
|
|
97
|
-
|
|
174
|
+
data: {
|
|
175
|
+
...getDefaultBooleanValues(spec),
|
|
176
|
+
...getFieldDefaults(spec),
|
|
177
|
+
...initialData
|
|
178
|
+
},
|
|
98
179
|
touched: {},
|
|
99
180
|
isSubmitting: false,
|
|
100
181
|
isSubmitted: false,
|
|
@@ -104,6 +185,30 @@ function useForma(options) {
|
|
|
104
185
|
const stateDataRef = useRef(state.data);
|
|
105
186
|
stateDataRef.current = state.data;
|
|
106
187
|
const hasInitialized = useRef(false);
|
|
188
|
+
const emitterRef = useRef(new FormaEventEmitter());
|
|
189
|
+
const onEventsRef = useRef(onEvents);
|
|
190
|
+
onEventsRef.current = onEvents;
|
|
191
|
+
const pendingEventsRef = useRef([]);
|
|
192
|
+
const isFiringEventsRef = useRef(false);
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
const emitter = emitterRef.current;
|
|
195
|
+
return () => {
|
|
196
|
+
emitter.clear();
|
|
197
|
+
};
|
|
198
|
+
}, []);
|
|
199
|
+
const fireEvent = useCallback(
|
|
200
|
+
(event, payload) => {
|
|
201
|
+
var _a;
|
|
202
|
+
try {
|
|
203
|
+
const handler = (_a = onEventsRef.current) == null ? void 0 : _a[event];
|
|
204
|
+
if (handler) handler(payload);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error(`[forma] Error in "${event}" event handler:`, error);
|
|
207
|
+
}
|
|
208
|
+
emitterRef.current.fire(event, payload);
|
|
209
|
+
},
|
|
210
|
+
[]
|
|
211
|
+
);
|
|
107
212
|
const computed = useMemo(
|
|
108
213
|
() => calculate(state.data, spec),
|
|
109
214
|
[state.data, spec]
|
|
@@ -184,21 +289,49 @@ function useForma(options) {
|
|
|
184
289
|
},
|
|
185
290
|
[state.data]
|
|
186
291
|
);
|
|
292
|
+
const getValueAtPath = useCallback((path) => {
|
|
293
|
+
const parts = path.replace(/\[(\d+)\]/g, ".$1").split(".");
|
|
294
|
+
let value = stateDataRef.current;
|
|
295
|
+
for (const part of parts) {
|
|
296
|
+
if (value === null || value === void 0) return void 0;
|
|
297
|
+
value = value[part];
|
|
298
|
+
}
|
|
299
|
+
return value;
|
|
300
|
+
}, []);
|
|
301
|
+
const queueFieldChangedEvent = useCallback(
|
|
302
|
+
(path, value, source) => {
|
|
303
|
+
if (isFiringEventsRef.current) return;
|
|
304
|
+
const previousValue = getValueAtPath(path);
|
|
305
|
+
if (previousValue === value) return;
|
|
306
|
+
pendingEventsRef.current.push({
|
|
307
|
+
event: "fieldChanged",
|
|
308
|
+
payload: { path, value, previousValue, source }
|
|
309
|
+
});
|
|
310
|
+
},
|
|
311
|
+
[getValueAtPath]
|
|
312
|
+
);
|
|
187
313
|
const setFieldValue = useCallback(
|
|
188
314
|
(path, value) => {
|
|
315
|
+
queueFieldChangedEvent(path, value, "user");
|
|
189
316
|
setNestedValue(path, value);
|
|
190
317
|
if (validateOn === "change") {
|
|
191
318
|
dispatch({ type: "SET_FIELD_TOUCHED", field: path, touched: true });
|
|
192
319
|
}
|
|
193
320
|
},
|
|
194
|
-
[validateOn, setNestedValue]
|
|
321
|
+
[validateOn, setNestedValue, queueFieldChangedEvent]
|
|
195
322
|
);
|
|
196
323
|
const setFieldTouched = useCallback((path, touched = true) => {
|
|
197
324
|
dispatch({ type: "SET_FIELD_TOUCHED", field: path, touched });
|
|
198
325
|
}, []);
|
|
199
|
-
const setValues = useCallback(
|
|
200
|
-
|
|
201
|
-
|
|
326
|
+
const setValues = useCallback(
|
|
327
|
+
(values) => {
|
|
328
|
+
for (const [key, value] of Object.entries(values)) {
|
|
329
|
+
queueFieldChangedEvent(key, value, "setValues");
|
|
330
|
+
}
|
|
331
|
+
dispatch({ type: "SET_VALUES", values });
|
|
332
|
+
},
|
|
333
|
+
[queueFieldChangedEvent]
|
|
334
|
+
);
|
|
202
335
|
const validateField = useCallback(
|
|
203
336
|
(path) => {
|
|
204
337
|
return validation.errors.filter((e) => e.field === path);
|
|
@@ -209,19 +342,84 @@ function useForma(options) {
|
|
|
209
342
|
return validation;
|
|
210
343
|
}, [validation]);
|
|
211
344
|
const submitForm = useCallback(async () => {
|
|
345
|
+
var _a;
|
|
212
346
|
dispatch({ type: "SET_SUBMITTING", isSubmitting: true });
|
|
347
|
+
const submissionData = { ...state.data };
|
|
348
|
+
let postSubmitPayload;
|
|
213
349
|
try {
|
|
214
|
-
|
|
215
|
-
|
|
350
|
+
const preSubmitPayload = {
|
|
351
|
+
data: submissionData,
|
|
352
|
+
computed: { ...computed }
|
|
353
|
+
};
|
|
354
|
+
const declarativePreSubmit = (_a = onEventsRef.current) == null ? void 0 : _a.preSubmit;
|
|
355
|
+
if (declarativePreSubmit) {
|
|
356
|
+
await declarativePreSubmit(preSubmitPayload);
|
|
357
|
+
}
|
|
358
|
+
if (emitterRef.current.hasListeners("preSubmit")) {
|
|
359
|
+
await emitterRef.current.fireAsync("preSubmit", preSubmitPayload);
|
|
360
|
+
}
|
|
361
|
+
if (!immediateValidation.valid) {
|
|
362
|
+
postSubmitPayload = {
|
|
363
|
+
data: submissionData,
|
|
364
|
+
success: false,
|
|
365
|
+
validationErrors: immediateValidation.errors
|
|
366
|
+
};
|
|
367
|
+
} else if (onSubmit) {
|
|
368
|
+
try {
|
|
369
|
+
await onSubmit(submissionData);
|
|
370
|
+
postSubmitPayload = { data: submissionData, success: true };
|
|
371
|
+
} catch (error) {
|
|
372
|
+
postSubmitPayload = {
|
|
373
|
+
data: submissionData,
|
|
374
|
+
success: false,
|
|
375
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
} else {
|
|
379
|
+
postSubmitPayload = { data: submissionData, success: true };
|
|
216
380
|
}
|
|
217
381
|
dispatch({ type: "SET_SUBMITTED", isSubmitted: true });
|
|
218
382
|
} finally {
|
|
219
383
|
dispatch({ type: "SET_SUBMITTING", isSubmitting: false });
|
|
384
|
+
if (postSubmitPayload) {
|
|
385
|
+
fireEvent("postSubmit", postSubmitPayload);
|
|
386
|
+
}
|
|
220
387
|
}
|
|
221
|
-
}, [immediateValidation, onSubmit, state.data]);
|
|
388
|
+
}, [immediateValidation, onSubmit, state.data, computed, fireEvent]);
|
|
222
389
|
const resetForm = useCallback(() => {
|
|
223
|
-
|
|
224
|
-
|
|
390
|
+
const resetData = {
|
|
391
|
+
...getDefaultBooleanValues(spec),
|
|
392
|
+
...getFieldDefaults(spec),
|
|
393
|
+
...initialData
|
|
394
|
+
};
|
|
395
|
+
if (!isFiringEventsRef.current) {
|
|
396
|
+
const currentData = stateDataRef.current;
|
|
397
|
+
const allKeys = /* @__PURE__ */ new Set([
|
|
398
|
+
...Object.keys(currentData),
|
|
399
|
+
...Object.keys(resetData)
|
|
400
|
+
]);
|
|
401
|
+
for (const key of allKeys) {
|
|
402
|
+
const currentVal = currentData[key];
|
|
403
|
+
const resetVal = resetData[key];
|
|
404
|
+
if (currentVal !== resetVal) {
|
|
405
|
+
pendingEventsRef.current.push({
|
|
406
|
+
event: "fieldChanged",
|
|
407
|
+
payload: {
|
|
408
|
+
path: key,
|
|
409
|
+
value: resetVal,
|
|
410
|
+
previousValue: currentVal,
|
|
411
|
+
source: "reset"
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
pendingEventsRef.current.push({
|
|
417
|
+
event: "formReset",
|
|
418
|
+
payload: {}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
dispatch({ type: "RESET", initialData: resetData });
|
|
422
|
+
}, [spec, initialData]);
|
|
225
423
|
const wizard = useMemo(() => {
|
|
226
424
|
if (!spec.pages || spec.pages.length === 0) return null;
|
|
227
425
|
const pageVisibility = getPageVisibility(state.data, spec, { computed });
|
|
@@ -251,16 +449,44 @@ function useForma(options) {
|
|
|
251
449
|
currentPage,
|
|
252
450
|
goToPage: (index) => {
|
|
253
451
|
const validIndex = Math.min(Math.max(0, index), maxPageIndex);
|
|
254
|
-
|
|
452
|
+
if (validIndex !== clampedPageIndex) {
|
|
453
|
+
dispatch({ type: "SET_PAGE", page: validIndex });
|
|
454
|
+
const newPage = visiblePages[validIndex];
|
|
455
|
+
if (newPage) {
|
|
456
|
+
fireEvent("pageChanged", {
|
|
457
|
+
fromIndex: clampedPageIndex,
|
|
458
|
+
toIndex: validIndex,
|
|
459
|
+
page: newPage
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
255
463
|
},
|
|
256
464
|
nextPage: () => {
|
|
257
465
|
if (hasNextPage) {
|
|
258
|
-
|
|
466
|
+
const toIndex = clampedPageIndex + 1;
|
|
467
|
+
dispatch({ type: "SET_PAGE", page: toIndex });
|
|
468
|
+
const newPage = visiblePages[toIndex];
|
|
469
|
+
if (newPage) {
|
|
470
|
+
fireEvent("pageChanged", {
|
|
471
|
+
fromIndex: clampedPageIndex,
|
|
472
|
+
toIndex,
|
|
473
|
+
page: newPage
|
|
474
|
+
});
|
|
475
|
+
}
|
|
259
476
|
}
|
|
260
477
|
},
|
|
261
478
|
previousPage: () => {
|
|
262
479
|
if (hasPreviousPage) {
|
|
263
|
-
|
|
480
|
+
const toIndex = clampedPageIndex - 1;
|
|
481
|
+
dispatch({ type: "SET_PAGE", page: toIndex });
|
|
482
|
+
const newPage = visiblePages[toIndex];
|
|
483
|
+
if (newPage) {
|
|
484
|
+
fireEvent("pageChanged", {
|
|
485
|
+
fromIndex: clampedPageIndex,
|
|
486
|
+
toIndex,
|
|
487
|
+
page: newPage
|
|
488
|
+
});
|
|
489
|
+
}
|
|
264
490
|
}
|
|
265
491
|
},
|
|
266
492
|
hasNextPage,
|
|
@@ -291,40 +517,51 @@ function useForma(options) {
|
|
|
291
517
|
return pageErrors.length === 0;
|
|
292
518
|
}
|
|
293
519
|
};
|
|
294
|
-
}, [spec, state.data, state.currentPage, computed, validation, visibility]);
|
|
295
|
-
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (parts.length === 1) {
|
|
307
|
-
dispatch({ type: "SET_FIELD_VALUE", field: path, value });
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
const newData = { ...stateDataRef.current };
|
|
311
|
-
let current = newData;
|
|
312
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
313
|
-
const part = parts[i];
|
|
314
|
-
const nextPart = parts[i + 1];
|
|
315
|
-
const isNextArrayIndex = /^\d+$/.test(nextPart);
|
|
316
|
-
if (current[part] === void 0) {
|
|
317
|
-
current[part] = isNextArrayIndex ? [] : {};
|
|
318
|
-
} else if (Array.isArray(current[part])) {
|
|
319
|
-
current[part] = [...current[part]];
|
|
320
|
-
} else {
|
|
321
|
-
current[part] = { ...current[part] };
|
|
520
|
+
}, [spec, state.data, state.currentPage, computed, validation, visibility, fireEvent]);
|
|
521
|
+
useEffect(() => {
|
|
522
|
+
const events = pendingEventsRef.current;
|
|
523
|
+
if (events.length === 0) return;
|
|
524
|
+
pendingEventsRef.current = [];
|
|
525
|
+
isFiringEventsRef.current = true;
|
|
526
|
+
try {
|
|
527
|
+
for (const pending of events) {
|
|
528
|
+
fireEvent(
|
|
529
|
+
pending.event,
|
|
530
|
+
pending.payload
|
|
531
|
+
);
|
|
322
532
|
}
|
|
323
|
-
|
|
533
|
+
} finally {
|
|
534
|
+
isFiringEventsRef.current = false;
|
|
324
535
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
536
|
+
});
|
|
537
|
+
const setValueAtPath = useCallback(
|
|
538
|
+
(path, value) => {
|
|
539
|
+
queueFieldChangedEvent(path, value, "user");
|
|
540
|
+
const parts = path.replace(/\[(\d+)\]/g, ".$1").split(".");
|
|
541
|
+
if (parts.length === 1) {
|
|
542
|
+
dispatch({ type: "SET_FIELD_VALUE", field: path, value });
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const newData = { ...stateDataRef.current };
|
|
546
|
+
let current = newData;
|
|
547
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
548
|
+
const part = parts[i];
|
|
549
|
+
const nextPart = parts[i + 1];
|
|
550
|
+
const isNextArrayIndex = /^\d+$/.test(nextPart);
|
|
551
|
+
if (current[part] === void 0) {
|
|
552
|
+
current[part] = isNextArrayIndex ? [] : {};
|
|
553
|
+
} else if (Array.isArray(current[part])) {
|
|
554
|
+
current[part] = [...current[part]];
|
|
555
|
+
} else {
|
|
556
|
+
current[part] = { ...current[part] };
|
|
557
|
+
}
|
|
558
|
+
current = current[part];
|
|
559
|
+
}
|
|
560
|
+
current[parts[parts.length - 1]] = value;
|
|
561
|
+
dispatch({ type: "SET_VALUES", values: newData });
|
|
562
|
+
},
|
|
563
|
+
[queueFieldChangedEvent]
|
|
564
|
+
);
|
|
328
565
|
const fieldHandlers = useRef(/* @__PURE__ */ new Map());
|
|
329
566
|
useEffect(() => {
|
|
330
567
|
const validFields = new Set(spec.fieldOrder);
|
|
@@ -544,6 +781,10 @@ function useForma(options) {
|
|
|
544
781
|
optionsVisibility
|
|
545
782
|
]
|
|
546
783
|
);
|
|
784
|
+
const on = useCallback(
|
|
785
|
+
(event, listener) => emitterRef.current.on(event, listener),
|
|
786
|
+
[]
|
|
787
|
+
);
|
|
547
788
|
return useMemo(
|
|
548
789
|
() => ({
|
|
549
790
|
data: state.data,
|
|
@@ -568,6 +809,7 @@ function useForma(options) {
|
|
|
568
809
|
validateForm,
|
|
569
810
|
submitForm,
|
|
570
811
|
resetForm,
|
|
812
|
+
on,
|
|
571
813
|
getFieldProps,
|
|
572
814
|
getSelectFieldProps,
|
|
573
815
|
getArrayHelpers
|
|
@@ -595,6 +837,7 @@ function useForma(options) {
|
|
|
595
837
|
validateForm,
|
|
596
838
|
submitForm,
|
|
597
839
|
resetForm,
|
|
840
|
+
on,
|
|
598
841
|
getFieldProps,
|
|
599
842
|
getSelectFieldProps,
|
|
600
843
|
getArrayHelpers
|
|
@@ -618,7 +861,9 @@ var FormaContext = createContext(null);
|
|
|
618
861
|
function useFormaContext() {
|
|
619
862
|
const context = useContext(FormaContext);
|
|
620
863
|
if (!context) {
|
|
621
|
-
throw new Error(
|
|
864
|
+
throw new Error(
|
|
865
|
+
"useFormaContext must be used within a FormaContext.Provider"
|
|
866
|
+
);
|
|
622
867
|
}
|
|
623
868
|
return context;
|
|
624
869
|
}
|
|
@@ -699,7 +944,9 @@ function getNumberConstraints(schema) {
|
|
|
699
944
|
function createDefaultItem(itemFields) {
|
|
700
945
|
const item = {};
|
|
701
946
|
for (const [fieldName, fieldDef] of Object.entries(itemFields)) {
|
|
702
|
-
if (fieldDef.
|
|
947
|
+
if (fieldDef.defaultValue !== void 0) {
|
|
948
|
+
item[fieldName] = fieldDef.defaultValue;
|
|
949
|
+
} else if (fieldDef.type === "boolean") {
|
|
703
950
|
item[fieldName] = false;
|
|
704
951
|
} else if (fieldDef.type === "number" || fieldDef.type === "integer") {
|
|
705
952
|
item[fieldName] = null;
|
|
@@ -1006,7 +1253,9 @@ function getNumberConstraints2(schema) {
|
|
|
1006
1253
|
function createDefaultItem2(itemFields) {
|
|
1007
1254
|
const item = {};
|
|
1008
1255
|
for (const [fieldName, fieldDef] of Object.entries(itemFields)) {
|
|
1009
|
-
if (fieldDef.
|
|
1256
|
+
if (fieldDef.defaultValue !== void 0) {
|
|
1257
|
+
item[fieldName] = fieldDef.defaultValue;
|
|
1258
|
+
} else if (fieldDef.type === "boolean") {
|
|
1010
1259
|
item[fieldName] = false;
|
|
1011
1260
|
} else if (fieldDef.type === "number" || fieldDef.type === "integer") {
|
|
1012
1261
|
item[fieldName] = null;
|
|
@@ -1227,7 +1476,10 @@ function FieldRenderer({
|
|
|
1227
1476
|
// src/ErrorBoundary.tsx
|
|
1228
1477
|
import React3 from "react";
|
|
1229
1478
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1230
|
-
function DefaultErrorFallback({
|
|
1479
|
+
function DefaultErrorFallback({
|
|
1480
|
+
error,
|
|
1481
|
+
onReset
|
|
1482
|
+
}) {
|
|
1231
1483
|
return /* @__PURE__ */ jsxs2("div", { className: "forma-error-boundary", role: "alert", children: [
|
|
1232
1484
|
/* @__PURE__ */ jsx3("h3", { children: "Something went wrong" }),
|
|
1233
1485
|
/* @__PURE__ */ jsx3("p", { children: "An error occurred while rendering the form." }),
|