@fogpipe/forma-react 0.12.0-alpha.1 → 0.12.0-alpha.2
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/index.d.ts +1 -1
- package/dist/index.js +521 -351
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/FieldRenderer.tsx +82 -20
- package/src/FormRenderer.tsx +320 -181
- package/src/__tests__/FieldRenderer.test.tsx +136 -20
- package/src/__tests__/FormRenderer.test.tsx +264 -85
- package/src/useForma.ts +382 -249
package/dist/index.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
// src/useForma.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useReducer,
|
|
7
|
+
useRef,
|
|
8
|
+
useState
|
|
9
|
+
} from "react";
|
|
3
10
|
import { isAdornableField } from "@fogpipe/forma-core";
|
|
4
11
|
import {
|
|
5
12
|
getVisibility,
|
|
@@ -66,7 +73,15 @@ function getDefaultBooleanValues(spec) {
|
|
|
66
73
|
return defaults;
|
|
67
74
|
}
|
|
68
75
|
function useForma(options) {
|
|
69
|
-
const {
|
|
76
|
+
const {
|
|
77
|
+
spec: inputSpec,
|
|
78
|
+
initialData = {},
|
|
79
|
+
onSubmit,
|
|
80
|
+
onChange,
|
|
81
|
+
validateOn = "blur",
|
|
82
|
+
referenceData,
|
|
83
|
+
validationDebounceMs = 0
|
|
84
|
+
} = options;
|
|
70
85
|
const spec = useMemo(() => {
|
|
71
86
|
if (!referenceData) return inputSpec;
|
|
72
87
|
return {
|
|
@@ -136,33 +151,39 @@ function useForma(options) {
|
|
|
136
151
|
hasInitialized.current = true;
|
|
137
152
|
}
|
|
138
153
|
}, [state.data, computed, onChange]);
|
|
139
|
-
const setNestedValue = useCallback(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const buildNestedObject = (data, pathParts, val) => {
|
|
146
|
-
const result = { ...data };
|
|
147
|
-
let current = result;
|
|
148
|
-
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
149
|
-
const part = pathParts[i];
|
|
150
|
-
const nextPart = pathParts[i + 1];
|
|
151
|
-
const isNextArrayIndex = /^\d+$/.test(nextPart);
|
|
152
|
-
if (current[part] === void 0) {
|
|
153
|
-
current[part] = isNextArrayIndex ? [] : {};
|
|
154
|
-
} else if (Array.isArray(current[part])) {
|
|
155
|
-
current[part] = [...current[part]];
|
|
156
|
-
} else {
|
|
157
|
-
current[part] = { ...current[part] };
|
|
158
|
-
}
|
|
159
|
-
current = current[part];
|
|
154
|
+
const setNestedValue = useCallback(
|
|
155
|
+
(path, value) => {
|
|
156
|
+
const parts = path.replace(/\[(\d+)\]/g, ".$1").split(".");
|
|
157
|
+
if (parts.length === 1) {
|
|
158
|
+
dispatch({ type: "SET_FIELD_VALUE", field: path, value });
|
|
159
|
+
return;
|
|
160
160
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
const buildNestedObject = (data, pathParts, val) => {
|
|
162
|
+
const result = { ...data };
|
|
163
|
+
let current = result;
|
|
164
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
165
|
+
const part = pathParts[i];
|
|
166
|
+
const nextPart = pathParts[i + 1];
|
|
167
|
+
const isNextArrayIndex = /^\d+$/.test(nextPart);
|
|
168
|
+
if (current[part] === void 0) {
|
|
169
|
+
current[part] = isNextArrayIndex ? [] : {};
|
|
170
|
+
} else if (Array.isArray(current[part])) {
|
|
171
|
+
current[part] = [...current[part]];
|
|
172
|
+
} else {
|
|
173
|
+
current[part] = { ...current[part] };
|
|
174
|
+
}
|
|
175
|
+
current = current[part];
|
|
176
|
+
}
|
|
177
|
+
current[pathParts[pathParts.length - 1]] = val;
|
|
178
|
+
return result;
|
|
179
|
+
};
|
|
180
|
+
dispatch({
|
|
181
|
+
type: "SET_VALUES",
|
|
182
|
+
values: buildNestedObject(state.data, parts, value)
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
[state.data]
|
|
186
|
+
);
|
|
166
187
|
const setFieldValue = useCallback(
|
|
167
188
|
(path, value) => {
|
|
168
189
|
setNestedValue(path, value);
|
|
@@ -213,7 +234,10 @@ function useForma(options) {
|
|
|
213
234
|
}));
|
|
214
235
|
const visiblePages = pages.filter((p) => p.visible);
|
|
215
236
|
const maxPageIndex = Math.max(0, visiblePages.length - 1);
|
|
216
|
-
const clampedPageIndex = Math.min(
|
|
237
|
+
const clampedPageIndex = Math.min(
|
|
238
|
+
Math.max(0, state.currentPage),
|
|
239
|
+
maxPageIndex
|
|
240
|
+
);
|
|
217
241
|
if (clampedPageIndex !== state.currentPage && visiblePages.length > 0) {
|
|
218
242
|
dispatch({ type: "SET_PAGE", page: clampedPageIndex });
|
|
219
243
|
}
|
|
@@ -321,193 +345,271 @@ function useForma(options) {
|
|
|
321
345
|
}
|
|
322
346
|
}
|
|
323
347
|
}, [spec]);
|
|
324
|
-
const getFieldHandlers = useCallback(
|
|
325
|
-
|
|
326
|
-
fieldHandlers.current.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
return fieldHandlers.current.get(path);
|
|
332
|
-
}, [setValueAtPath, setFieldTouched]);
|
|
333
|
-
const getFieldProps = useCallback((path) => {
|
|
334
|
-
var _a;
|
|
335
|
-
const fieldDef = spec.fields[path];
|
|
336
|
-
const handlers = getFieldHandlers(path);
|
|
337
|
-
let fieldType = (fieldDef == null ? void 0 : fieldDef.type) || "text";
|
|
338
|
-
if (!fieldType || fieldType === "computed") {
|
|
339
|
-
const schemaProperty2 = spec.schema.properties[path];
|
|
340
|
-
if (schemaProperty2) {
|
|
341
|
-
if (schemaProperty2.type === "number") fieldType = "number";
|
|
342
|
-
else if (schemaProperty2.type === "integer") fieldType = "integer";
|
|
343
|
-
else if (schemaProperty2.type === "boolean") fieldType = "boolean";
|
|
344
|
-
else if (schemaProperty2.type === "array") fieldType = "array";
|
|
345
|
-
else if (schemaProperty2.type === "object") fieldType = "object";
|
|
346
|
-
else if ("enum" in schemaProperty2 && schemaProperty2.enum) fieldType = "select";
|
|
347
|
-
else if ("format" in schemaProperty2) {
|
|
348
|
-
if (schemaProperty2.format === "date") fieldType = "date";
|
|
349
|
-
else if (schemaProperty2.format === "date-time") fieldType = "datetime";
|
|
350
|
-
else if (schemaProperty2.format === "email") fieldType = "email";
|
|
351
|
-
else if (schemaProperty2.format === "uri") fieldType = "url";
|
|
352
|
-
}
|
|
348
|
+
const getFieldHandlers = useCallback(
|
|
349
|
+
(path) => {
|
|
350
|
+
if (!fieldHandlers.current.has(path)) {
|
|
351
|
+
fieldHandlers.current.set(path, {
|
|
352
|
+
onChange: (value) => setValueAtPath(path, value),
|
|
353
|
+
onBlur: () => setFieldTouched(path)
|
|
354
|
+
});
|
|
353
355
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const isRequired = required[path] ?? false;
|
|
361
|
-
const schemaProperty = spec.schema.properties[path];
|
|
362
|
-
const isBooleanField = (schemaProperty == null ? void 0 : schemaProperty.type) === "boolean" || (fieldDef == null ? void 0 : fieldDef.type) === "boolean";
|
|
363
|
-
const hasValidationRules = (((_a = fieldDef == null ? void 0 : fieldDef.validations) == null ? void 0 : _a.length) ?? 0) > 0;
|
|
364
|
-
const showRequiredIndicator = isRequired && (!isBooleanField || hasValidationRules);
|
|
365
|
-
const adornerProps = fieldDef && isAdornableField(fieldDef) ? { prefix: fieldDef.prefix, suffix: fieldDef.suffix } : {};
|
|
366
|
-
return {
|
|
367
|
-
name: path,
|
|
368
|
-
value: getValueAtPath(path),
|
|
369
|
-
type: fieldType,
|
|
370
|
-
label: (fieldDef == null ? void 0 : fieldDef.label) || path.charAt(0).toUpperCase() + path.slice(1),
|
|
371
|
-
description: fieldDef == null ? void 0 : fieldDef.description,
|
|
372
|
-
placeholder: fieldDef == null ? void 0 : fieldDef.placeholder,
|
|
373
|
-
visible: visibility[path] !== false,
|
|
374
|
-
enabled: enabled[path] !== false,
|
|
375
|
-
readonly: readonly[path] ?? false,
|
|
376
|
-
required: isRequired,
|
|
377
|
-
showRequiredIndicator,
|
|
378
|
-
touched: isTouched,
|
|
379
|
-
errors: displayedErrors,
|
|
380
|
-
onChange: handlers.onChange,
|
|
381
|
-
onBlur: handlers.onBlur,
|
|
382
|
-
// ARIA accessibility attributes
|
|
383
|
-
"aria-invalid": hasErrors || void 0,
|
|
384
|
-
"aria-describedby": hasErrors ? `${path}-error` : void 0,
|
|
385
|
-
"aria-required": isRequired || void 0,
|
|
386
|
-
// Adorner props (only for adornable field types)
|
|
387
|
-
...adornerProps,
|
|
388
|
-
// Presentation variant
|
|
389
|
-
variant: fieldDef == null ? void 0 : fieldDef.variant,
|
|
390
|
-
variantConfig: fieldDef == null ? void 0 : fieldDef.variantConfig
|
|
391
|
-
};
|
|
392
|
-
}, [spec, state.touched, state.isSubmitted, visibility, enabled, readonly, required, validation.errors, validateOn, getValueAtPath, getFieldHandlers]);
|
|
393
|
-
const getSelectFieldProps = useCallback((path) => {
|
|
394
|
-
const baseProps = getFieldProps(path);
|
|
395
|
-
const visibleOptions = optionsVisibility[path] ?? [];
|
|
396
|
-
return {
|
|
397
|
-
...baseProps,
|
|
398
|
-
options: visibleOptions
|
|
399
|
-
};
|
|
400
|
-
}, [getFieldProps, optionsVisibility]);
|
|
401
|
-
const getArrayHelpers = useCallback((path) => {
|
|
402
|
-
const fieldDef = spec.fields[path];
|
|
403
|
-
const currentValue = getValueAtPath(path) ?? [];
|
|
404
|
-
const arrayDef = (fieldDef == null ? void 0 : fieldDef.type) === "array" ? fieldDef : void 0;
|
|
405
|
-
const minItems = (arrayDef == null ? void 0 : arrayDef.minItems) ?? 0;
|
|
406
|
-
const maxItems = (arrayDef == null ? void 0 : arrayDef.maxItems) ?? Infinity;
|
|
407
|
-
const canAdd = currentValue.length < maxItems;
|
|
408
|
-
const canRemove = currentValue.length > minItems;
|
|
409
|
-
const getItemFieldProps = (index, fieldName) => {
|
|
356
|
+
return fieldHandlers.current.get(path);
|
|
357
|
+
},
|
|
358
|
+
[setValueAtPath, setFieldTouched]
|
|
359
|
+
);
|
|
360
|
+
const getFieldProps = useCallback(
|
|
361
|
+
(path) => {
|
|
410
362
|
var _a;
|
|
411
|
-
const
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
363
|
+
const fieldDef = spec.fields[path];
|
|
364
|
+
const handlers = getFieldHandlers(path);
|
|
365
|
+
let fieldType = (fieldDef == null ? void 0 : fieldDef.type) || "text";
|
|
366
|
+
if (!fieldType || fieldType === "computed") {
|
|
367
|
+
const schemaProperty2 = spec.schema.properties[path];
|
|
368
|
+
if (schemaProperty2) {
|
|
369
|
+
if (schemaProperty2.type === "number") fieldType = "number";
|
|
370
|
+
else if (schemaProperty2.type === "integer") fieldType = "integer";
|
|
371
|
+
else if (schemaProperty2.type === "boolean") fieldType = "boolean";
|
|
372
|
+
else if (schemaProperty2.type === "array") fieldType = "array";
|
|
373
|
+
else if (schemaProperty2.type === "object") fieldType = "object";
|
|
374
|
+
else if ("enum" in schemaProperty2 && schemaProperty2.enum)
|
|
375
|
+
fieldType = "select";
|
|
376
|
+
else if ("format" in schemaProperty2) {
|
|
377
|
+
if (schemaProperty2.format === "date") fieldType = "date";
|
|
378
|
+
else if (schemaProperty2.format === "date-time")
|
|
379
|
+
fieldType = "datetime";
|
|
380
|
+
else if (schemaProperty2.format === "email") fieldType = "email";
|
|
381
|
+
else if (schemaProperty2.format === "uri") fieldType = "url";
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const fieldErrors = validation.errors.filter((e) => e.field === path);
|
|
386
|
+
const isTouched = state.touched[path] ?? false;
|
|
418
387
|
const showErrors = validateOn === "change" || validateOn === "blur" && isTouched || state.isSubmitted;
|
|
419
|
-
const
|
|
388
|
+
const displayedErrors = showErrors ? fieldErrors : [];
|
|
389
|
+
const hasErrors = displayedErrors.length > 0;
|
|
390
|
+
const isRequired = required[path] ?? false;
|
|
391
|
+
const schemaProperty = spec.schema.properties[path];
|
|
392
|
+
const isBooleanField = (schemaProperty == null ? void 0 : schemaProperty.type) === "boolean" || (fieldDef == null ? void 0 : fieldDef.type) === "boolean";
|
|
393
|
+
const hasValidationRules = (((_a = fieldDef == null ? void 0 : fieldDef.validations) == null ? void 0 : _a.length) ?? 0) > 0;
|
|
394
|
+
const showRequiredIndicator = isRequired && (!isBooleanField || hasValidationRules);
|
|
395
|
+
const adornerProps = fieldDef && isAdornableField(fieldDef) ? { prefix: fieldDef.prefix, suffix: fieldDef.suffix } : {};
|
|
420
396
|
return {
|
|
421
|
-
name:
|
|
422
|
-
value:
|
|
423
|
-
type:
|
|
424
|
-
label: (
|
|
425
|
-
description:
|
|
426
|
-
placeholder:
|
|
427
|
-
visible:
|
|
397
|
+
name: path,
|
|
398
|
+
value: getValueAtPath(path),
|
|
399
|
+
type: fieldType,
|
|
400
|
+
label: (fieldDef == null ? void 0 : fieldDef.label) || path.charAt(0).toUpperCase() + path.slice(1),
|
|
401
|
+
description: fieldDef == null ? void 0 : fieldDef.description,
|
|
402
|
+
placeholder: fieldDef == null ? void 0 : fieldDef.placeholder,
|
|
403
|
+
visible: visibility[path] !== false,
|
|
428
404
|
enabled: enabled[path] !== false,
|
|
429
|
-
readonly: readonly[
|
|
430
|
-
required:
|
|
431
|
-
|
|
432
|
-
showRequiredIndicator: false,
|
|
433
|
-
// Item fields don't show required indicator
|
|
405
|
+
readonly: readonly[path] ?? false,
|
|
406
|
+
required: isRequired,
|
|
407
|
+
showRequiredIndicator,
|
|
434
408
|
touched: isTouched,
|
|
435
|
-
errors:
|
|
409
|
+
errors: displayedErrors,
|
|
436
410
|
onChange: handlers.onChange,
|
|
437
411
|
onBlur: handlers.onBlur,
|
|
412
|
+
// ARIA accessibility attributes
|
|
413
|
+
"aria-invalid": hasErrors || void 0,
|
|
414
|
+
"aria-describedby": hasErrors ? `${path}-error` : void 0,
|
|
415
|
+
"aria-required": isRequired || void 0,
|
|
416
|
+
// Adorner props (only for adornable field types)
|
|
417
|
+
...adornerProps,
|
|
418
|
+
// Presentation variant
|
|
419
|
+
variant: fieldDef == null ? void 0 : fieldDef.variant,
|
|
420
|
+
variantConfig: fieldDef == null ? void 0 : fieldDef.variantConfig
|
|
421
|
+
};
|
|
422
|
+
},
|
|
423
|
+
[
|
|
424
|
+
spec,
|
|
425
|
+
state.touched,
|
|
426
|
+
state.isSubmitted,
|
|
427
|
+
visibility,
|
|
428
|
+
enabled,
|
|
429
|
+
readonly,
|
|
430
|
+
required,
|
|
431
|
+
validation.errors,
|
|
432
|
+
validateOn,
|
|
433
|
+
getValueAtPath,
|
|
434
|
+
getFieldHandlers
|
|
435
|
+
]
|
|
436
|
+
);
|
|
437
|
+
const getSelectFieldProps = useCallback(
|
|
438
|
+
(path) => {
|
|
439
|
+
const baseProps = getFieldProps(path);
|
|
440
|
+
const visibleOptions = optionsVisibility[path] ?? [];
|
|
441
|
+
return {
|
|
442
|
+
...baseProps,
|
|
438
443
|
options: visibleOptions
|
|
439
444
|
};
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
445
|
+
},
|
|
446
|
+
[getFieldProps, optionsVisibility]
|
|
447
|
+
);
|
|
448
|
+
const getArrayHelpers = useCallback(
|
|
449
|
+
(path) => {
|
|
450
|
+
const fieldDef = spec.fields[path];
|
|
451
|
+
const currentValue = getValueAtPath(path) ?? [];
|
|
452
|
+
const arrayDef = (fieldDef == null ? void 0 : fieldDef.type) === "array" ? fieldDef : void 0;
|
|
453
|
+
const minItems = (arrayDef == null ? void 0 : arrayDef.minItems) ?? 0;
|
|
454
|
+
const maxItems = (arrayDef == null ? void 0 : arrayDef.maxItems) ?? Infinity;
|
|
455
|
+
const canAdd = currentValue.length < maxItems;
|
|
456
|
+
const canRemove = currentValue.length > minItems;
|
|
457
|
+
const getItemFieldProps = (index, fieldName) => {
|
|
458
|
+
var _a;
|
|
459
|
+
const itemPath = `${path}[${index}].${fieldName}`;
|
|
460
|
+
const itemFieldDef = (_a = arrayDef == null ? void 0 : arrayDef.itemFields) == null ? void 0 : _a[fieldName];
|
|
461
|
+
const handlers = getFieldHandlers(itemPath);
|
|
462
|
+
const item = currentValue[index] ?? {};
|
|
463
|
+
const itemValue = item[fieldName];
|
|
464
|
+
const fieldErrors = validation.errors.filter(
|
|
465
|
+
(e) => e.field === itemPath
|
|
466
|
+
);
|
|
467
|
+
const isTouched = state.touched[itemPath] ?? false;
|
|
468
|
+
const showErrors = validateOn === "change" || validateOn === "blur" && isTouched || state.isSubmitted;
|
|
469
|
+
const visibleOptions = optionsVisibility[itemPath];
|
|
470
|
+
return {
|
|
471
|
+
name: itemPath,
|
|
472
|
+
value: itemValue,
|
|
473
|
+
type: (itemFieldDef == null ? void 0 : itemFieldDef.type) || "text",
|
|
474
|
+
label: (itemFieldDef == null ? void 0 : itemFieldDef.label) || fieldName.charAt(0).toUpperCase() + fieldName.slice(1),
|
|
475
|
+
description: itemFieldDef == null ? void 0 : itemFieldDef.description,
|
|
476
|
+
placeholder: itemFieldDef == null ? void 0 : itemFieldDef.placeholder,
|
|
477
|
+
visible: true,
|
|
478
|
+
enabled: enabled[path] !== false,
|
|
479
|
+
readonly: readonly[itemPath] ?? false,
|
|
480
|
+
required: false,
|
|
481
|
+
// TODO: Evaluate item field required
|
|
482
|
+
showRequiredIndicator: false,
|
|
483
|
+
// Item fields don't show required indicator
|
|
484
|
+
touched: isTouched,
|
|
485
|
+
errors: showErrors ? fieldErrors : [],
|
|
486
|
+
onChange: handlers.onChange,
|
|
487
|
+
onBlur: handlers.onBlur,
|
|
488
|
+
options: visibleOptions
|
|
489
|
+
};
|
|
490
|
+
};
|
|
491
|
+
return {
|
|
492
|
+
items: currentValue,
|
|
493
|
+
push: (item) => {
|
|
494
|
+
if (canAdd) {
|
|
495
|
+
setValueAtPath(path, [...currentValue, item]);
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
remove: (index) => {
|
|
499
|
+
if (canRemove) {
|
|
500
|
+
const newArray = [...currentValue];
|
|
501
|
+
newArray.splice(index, 1);
|
|
502
|
+
setValueAtPath(path, newArray);
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
move: (from, to) => {
|
|
450
506
|
const newArray = [...currentValue];
|
|
451
|
-
newArray.splice(
|
|
507
|
+
const [item] = newArray.splice(from, 1);
|
|
508
|
+
newArray.splice(to, 0, item);
|
|
452
509
|
setValueAtPath(path, newArray);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
move: (from, to) => {
|
|
456
|
-
const newArray = [...currentValue];
|
|
457
|
-
const [item] = newArray.splice(from, 1);
|
|
458
|
-
newArray.splice(to, 0, item);
|
|
459
|
-
setValueAtPath(path, newArray);
|
|
460
|
-
},
|
|
461
|
-
swap: (indexA, indexB) => {
|
|
462
|
-
const newArray = [...currentValue];
|
|
463
|
-
[newArray[indexA], newArray[indexB]] = [newArray[indexB], newArray[indexA]];
|
|
464
|
-
setValueAtPath(path, newArray);
|
|
465
|
-
},
|
|
466
|
-
insert: (index, item) => {
|
|
467
|
-
if (canAdd) {
|
|
510
|
+
},
|
|
511
|
+
swap: (indexA, indexB) => {
|
|
468
512
|
const newArray = [...currentValue];
|
|
469
|
-
newArray
|
|
513
|
+
[newArray[indexA], newArray[indexB]] = [
|
|
514
|
+
newArray[indexB],
|
|
515
|
+
newArray[indexA]
|
|
516
|
+
];
|
|
470
517
|
setValueAtPath(path, newArray);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
518
|
+
},
|
|
519
|
+
insert: (index, item) => {
|
|
520
|
+
if (canAdd) {
|
|
521
|
+
const newArray = [...currentValue];
|
|
522
|
+
newArray.splice(index, 0, item);
|
|
523
|
+
setValueAtPath(path, newArray);
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
getItemFieldProps,
|
|
527
|
+
minItems,
|
|
528
|
+
maxItems,
|
|
529
|
+
canAdd,
|
|
530
|
+
canRemove
|
|
531
|
+
};
|
|
532
|
+
},
|
|
533
|
+
[
|
|
534
|
+
spec.fields,
|
|
535
|
+
getValueAtPath,
|
|
536
|
+
setValueAtPath,
|
|
537
|
+
getFieldHandlers,
|
|
538
|
+
enabled,
|
|
539
|
+
readonly,
|
|
540
|
+
state.touched,
|
|
541
|
+
state.isSubmitted,
|
|
542
|
+
validation.errors,
|
|
543
|
+
validateOn,
|
|
544
|
+
optionsVisibility
|
|
545
|
+
]
|
|
546
|
+
);
|
|
547
|
+
return useMemo(
|
|
548
|
+
() => ({
|
|
549
|
+
data: state.data,
|
|
550
|
+
computed,
|
|
551
|
+
visibility,
|
|
552
|
+
required,
|
|
553
|
+
enabled,
|
|
554
|
+
readonly,
|
|
555
|
+
optionsVisibility,
|
|
556
|
+
touched: state.touched,
|
|
557
|
+
errors: validation.errors,
|
|
558
|
+
isValid: validation.valid,
|
|
559
|
+
isSubmitting: state.isSubmitting,
|
|
560
|
+
isSubmitted: state.isSubmitted,
|
|
561
|
+
isDirty: state.isDirty,
|
|
562
|
+
spec,
|
|
563
|
+
wizard,
|
|
564
|
+
setFieldValue,
|
|
565
|
+
setFieldTouched,
|
|
566
|
+
setValues,
|
|
567
|
+
validateField,
|
|
568
|
+
validateForm,
|
|
569
|
+
submitForm,
|
|
570
|
+
resetForm,
|
|
571
|
+
getFieldProps,
|
|
572
|
+
getSelectFieldProps,
|
|
573
|
+
getArrayHelpers
|
|
574
|
+
}),
|
|
575
|
+
[
|
|
576
|
+
state.data,
|
|
577
|
+
state.touched,
|
|
578
|
+
state.isSubmitting,
|
|
579
|
+
state.isSubmitted,
|
|
580
|
+
state.isDirty,
|
|
581
|
+
computed,
|
|
582
|
+
visibility,
|
|
583
|
+
required,
|
|
584
|
+
enabled,
|
|
585
|
+
readonly,
|
|
586
|
+
optionsVisibility,
|
|
587
|
+
validation.errors,
|
|
588
|
+
validation.valid,
|
|
589
|
+
spec,
|
|
590
|
+
wizard,
|
|
591
|
+
setFieldValue,
|
|
592
|
+
setFieldTouched,
|
|
593
|
+
setValues,
|
|
594
|
+
validateField,
|
|
595
|
+
validateForm,
|
|
596
|
+
submitForm,
|
|
597
|
+
resetForm,
|
|
598
|
+
getFieldProps,
|
|
599
|
+
getSelectFieldProps,
|
|
600
|
+
getArrayHelpers
|
|
601
|
+
]
|
|
602
|
+
);
|
|
507
603
|
}
|
|
508
604
|
|
|
509
605
|
// src/FormRenderer.tsx
|
|
510
|
-
import React, {
|
|
606
|
+
import React, {
|
|
607
|
+
forwardRef,
|
|
608
|
+
useImperativeHandle,
|
|
609
|
+
useRef as useRef2,
|
|
610
|
+
useMemo as useMemo2,
|
|
611
|
+
useCallback as useCallback2
|
|
612
|
+
} from "react";
|
|
511
613
|
import { isAdornableField as isAdornableField2, isSelectionField } from "@fogpipe/forma-core";
|
|
512
614
|
|
|
513
615
|
// src/context.ts
|
|
@@ -538,7 +640,14 @@ function DefaultLayout({ children, onSubmit, isSubmitting }) {
|
|
|
538
640
|
}
|
|
539
641
|
);
|
|
540
642
|
}
|
|
541
|
-
function DefaultFieldWrapper({
|
|
643
|
+
function DefaultFieldWrapper({
|
|
644
|
+
fieldPath,
|
|
645
|
+
field,
|
|
646
|
+
children,
|
|
647
|
+
errors,
|
|
648
|
+
showRequiredIndicator,
|
|
649
|
+
visible
|
|
650
|
+
}) {
|
|
542
651
|
if (!visible) return null;
|
|
543
652
|
const errorId = `${fieldPath}-error`;
|
|
544
653
|
const descriptionId = field.description ? `${fieldPath}-description` : void 0;
|
|
@@ -563,7 +672,11 @@ function DefaultFieldWrapper({ fieldPath, field, children, errors, showRequiredI
|
|
|
563
672
|
field.description && /* @__PURE__ */ jsx("p", { id: descriptionId, className: "field-description", children: field.description })
|
|
564
673
|
] });
|
|
565
674
|
}
|
|
566
|
-
function DefaultPageWrapper({
|
|
675
|
+
function DefaultPageWrapper({
|
|
676
|
+
title,
|
|
677
|
+
description,
|
|
678
|
+
children
|
|
679
|
+
}) {
|
|
567
680
|
return /* @__PURE__ */ jsxs("div", { className: "page-wrapper", children: [
|
|
568
681
|
/* @__PURE__ */ jsx("h2", { children: title }),
|
|
569
682
|
description && /* @__PURE__ */ jsx("p", { children: description }),
|
|
@@ -642,6 +755,20 @@ var FormRenderer = forwardRef(
|
|
|
642
755
|
}),
|
|
643
756
|
[forma, focusField, focusFirstError]
|
|
644
757
|
);
|
|
758
|
+
const {
|
|
759
|
+
data: formaData,
|
|
760
|
+
computed: formaComputed,
|
|
761
|
+
visibility: formaVisibility,
|
|
762
|
+
required: formaRequired,
|
|
763
|
+
enabled: formaEnabled,
|
|
764
|
+
readonly: formaReadonly,
|
|
765
|
+
optionsVisibility: formaOptionsVisibility,
|
|
766
|
+
touched: formaTouched,
|
|
767
|
+
errors: formaErrors,
|
|
768
|
+
setFieldValue,
|
|
769
|
+
setFieldTouched,
|
|
770
|
+
getArrayHelpers
|
|
771
|
+
} = forma;
|
|
645
772
|
const fieldsToRender = useMemo2(() => {
|
|
646
773
|
var _a;
|
|
647
774
|
if (spec.pages && spec.pages.length > 0 && forma.wizard) {
|
|
@@ -653,152 +780,179 @@ var FormRenderer = forwardRef(
|
|
|
653
780
|
}
|
|
654
781
|
return spec.fieldOrder;
|
|
655
782
|
}, [spec.pages, spec.fieldOrder, forma.wizard]);
|
|
656
|
-
const renderField = useCallback2(
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
required,
|
|
684
|
-
disabled,
|
|
685
|
-
errors,
|
|
686
|
-
onChange: (value) => forma.setFieldValue(fieldPath, value),
|
|
687
|
-
onBlur: () => forma.setFieldTouched(fieldPath),
|
|
688
|
-
// Convenience properties
|
|
689
|
-
visible: true,
|
|
690
|
-
// Always true since we already filtered for visibility
|
|
691
|
-
enabled: !disabled,
|
|
692
|
-
readonly: isReadonly,
|
|
693
|
-
label: fieldDef.label ?? fieldPath,
|
|
694
|
-
description: fieldDef.description,
|
|
695
|
-
placeholder: fieldDef.placeholder,
|
|
696
|
-
// Adorner properties (only for adornable field types)
|
|
697
|
-
...isAdornableField2(fieldDef) && {
|
|
698
|
-
prefix: fieldDef.prefix,
|
|
699
|
-
suffix: fieldDef.suffix
|
|
700
|
-
},
|
|
701
|
-
// Presentation variant
|
|
702
|
-
variant: fieldDef.variant,
|
|
703
|
-
variantConfig: fieldDef.variantConfig
|
|
704
|
-
};
|
|
705
|
-
let fieldProps = baseProps;
|
|
706
|
-
if (fieldType === "number" || fieldType === "integer") {
|
|
707
|
-
const constraints = getNumberConstraints(schemaProperty);
|
|
708
|
-
fieldProps = {
|
|
709
|
-
...baseProps,
|
|
710
|
-
fieldType,
|
|
711
|
-
value: baseProps.value,
|
|
712
|
-
onChange: baseProps.onChange,
|
|
713
|
-
...constraints
|
|
714
|
-
};
|
|
715
|
-
} else if (fieldType === "select" || fieldType === "multiselect") {
|
|
716
|
-
const selectOptions = isSelectionField(fieldDef) ? fieldDef.options : [];
|
|
717
|
-
fieldProps = {
|
|
718
|
-
...baseProps,
|
|
719
|
-
fieldType,
|
|
720
|
-
value: baseProps.value,
|
|
721
|
-
onChange: baseProps.onChange,
|
|
722
|
-
options: forma.optionsVisibility[fieldPath] ?? selectOptions ?? []
|
|
723
|
-
};
|
|
724
|
-
} else if (fieldType === "array" && fieldDef.type === "array" && fieldDef.itemFields) {
|
|
725
|
-
const arrayValue = Array.isArray(baseProps.value) ? baseProps.value : [];
|
|
726
|
-
const minItems = fieldDef.minItems ?? 0;
|
|
727
|
-
const maxItems = fieldDef.maxItems ?? Infinity;
|
|
728
|
-
const itemFieldDefs = fieldDef.itemFields;
|
|
729
|
-
const baseHelpers = forma.getArrayHelpers(fieldPath);
|
|
730
|
-
const pushWithDefault = (item) => {
|
|
731
|
-
const newItem = item ?? createDefaultItem(itemFieldDefs);
|
|
732
|
-
baseHelpers.push(newItem);
|
|
733
|
-
};
|
|
734
|
-
const getItemFieldPropsExtended = (index, fieldName) => {
|
|
735
|
-
const baseProps2 = baseHelpers.getItemFieldProps(index, fieldName);
|
|
736
|
-
const itemFieldDef = itemFieldDefs[fieldName];
|
|
737
|
-
const itemPath = `${fieldPath}[${index}].${fieldName}`;
|
|
738
|
-
return {
|
|
739
|
-
...baseProps2,
|
|
740
|
-
itemIndex: index,
|
|
741
|
-
fieldName,
|
|
742
|
-
options: forma.optionsVisibility[itemPath] ?? (itemFieldDef && isSelectionField(itemFieldDef) ? itemFieldDef.options : void 0)
|
|
743
|
-
};
|
|
744
|
-
};
|
|
745
|
-
const helpers = {
|
|
746
|
-
items: arrayValue,
|
|
747
|
-
push: pushWithDefault,
|
|
748
|
-
insert: baseHelpers.insert,
|
|
749
|
-
remove: baseHelpers.remove,
|
|
750
|
-
move: baseHelpers.move,
|
|
751
|
-
swap: baseHelpers.swap,
|
|
752
|
-
getItemFieldProps: getItemFieldPropsExtended,
|
|
753
|
-
minItems,
|
|
754
|
-
maxItems,
|
|
755
|
-
canAdd: arrayValue.length < maxItems,
|
|
756
|
-
canRemove: arrayValue.length > minItems
|
|
757
|
-
};
|
|
758
|
-
fieldProps = {
|
|
759
|
-
...baseProps,
|
|
760
|
-
fieldType: "array",
|
|
761
|
-
value: arrayValue,
|
|
762
|
-
onChange: baseProps.onChange,
|
|
763
|
-
helpers,
|
|
764
|
-
itemFields: itemFieldDefs,
|
|
765
|
-
minItems,
|
|
766
|
-
maxItems
|
|
767
|
-
};
|
|
768
|
-
} else if (fieldType === "display" && fieldDef.type === "display") {
|
|
769
|
-
const sourceValue = fieldDef.source ? forma.data[fieldDef.source] ?? forma.computed[fieldDef.source] : void 0;
|
|
770
|
-
const { onChange: _onChange, value: _value, ...displayBaseProps } = baseProps;
|
|
771
|
-
fieldProps = {
|
|
772
|
-
...displayBaseProps,
|
|
773
|
-
fieldType: "display",
|
|
774
|
-
content: fieldDef.content,
|
|
775
|
-
sourceValue,
|
|
776
|
-
format: fieldDef.format
|
|
777
|
-
};
|
|
778
|
-
} else {
|
|
779
|
-
fieldProps = {
|
|
780
|
-
...baseProps,
|
|
781
|
-
fieldType,
|
|
782
|
-
value: baseProps.value ?? "",
|
|
783
|
-
onChange: baseProps.onChange
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
const componentProps = { field: fieldProps, spec };
|
|
787
|
-
return /* @__PURE__ */ jsx(
|
|
788
|
-
FieldWrapper,
|
|
789
|
-
{
|
|
790
|
-
fieldPath,
|
|
783
|
+
const renderField = useCallback2(
|
|
784
|
+
(fieldPath) => {
|
|
785
|
+
var _a;
|
|
786
|
+
const fieldDef = spec.fields[fieldPath];
|
|
787
|
+
if (!fieldDef) return null;
|
|
788
|
+
const isVisible = formaVisibility[fieldPath] !== false;
|
|
789
|
+
if (!isVisible) {
|
|
790
|
+
return /* @__PURE__ */ jsx("div", { "data-field-path": fieldPath, hidden: true }, fieldPath);
|
|
791
|
+
}
|
|
792
|
+
const fieldType = fieldDef.type;
|
|
793
|
+
const componentKey = fieldType;
|
|
794
|
+
const Component = components[componentKey] || components.fallback;
|
|
795
|
+
if (!Component) {
|
|
796
|
+
console.warn(`No component found for field type: ${fieldType}`);
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
const errors = formaErrors.filter((e) => e.field === fieldPath);
|
|
800
|
+
const touched = formaTouched[fieldPath] ?? false;
|
|
801
|
+
const required = formaRequired[fieldPath] ?? false;
|
|
802
|
+
const disabled = formaEnabled[fieldPath] === false;
|
|
803
|
+
const schemaProperty = spec.schema.properties[fieldPath];
|
|
804
|
+
const isBooleanField = (schemaProperty == null ? void 0 : schemaProperty.type) === "boolean" || (fieldDef == null ? void 0 : fieldDef.type) === "boolean";
|
|
805
|
+
const hasValidationRules = (((_a = fieldDef == null ? void 0 : fieldDef.validations) == null ? void 0 : _a.length) ?? 0) > 0;
|
|
806
|
+
const showRequiredIndicator = required && (!isBooleanField || hasValidationRules);
|
|
807
|
+
const isReadonly = formaReadonly[fieldPath] ?? false;
|
|
808
|
+
const baseProps = {
|
|
809
|
+
name: fieldPath,
|
|
791
810
|
field: fieldDef,
|
|
792
|
-
|
|
811
|
+
value: formaData[fieldPath],
|
|
793
812
|
touched,
|
|
794
813
|
required,
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
814
|
+
disabled,
|
|
815
|
+
errors,
|
|
816
|
+
onChange: (value) => setFieldValue(fieldPath, value),
|
|
817
|
+
onBlur: () => setFieldTouched(fieldPath),
|
|
818
|
+
// Convenience properties
|
|
819
|
+
visible: true,
|
|
820
|
+
// Always true since we already filtered for visibility
|
|
821
|
+
enabled: !disabled,
|
|
822
|
+
readonly: isReadonly,
|
|
823
|
+
label: fieldDef.label ?? fieldPath,
|
|
824
|
+
description: fieldDef.description,
|
|
825
|
+
placeholder: fieldDef.placeholder,
|
|
826
|
+
// Adorner properties (only for adornable field types)
|
|
827
|
+
...isAdornableField2(fieldDef) && {
|
|
828
|
+
prefix: fieldDef.prefix,
|
|
829
|
+
suffix: fieldDef.suffix
|
|
830
|
+
},
|
|
831
|
+
// Presentation variant
|
|
832
|
+
variant: fieldDef.variant,
|
|
833
|
+
variantConfig: fieldDef.variantConfig
|
|
834
|
+
};
|
|
835
|
+
let fieldProps = baseProps;
|
|
836
|
+
if (fieldType === "number" || fieldType === "integer") {
|
|
837
|
+
const constraints = getNumberConstraints(schemaProperty);
|
|
838
|
+
fieldProps = {
|
|
839
|
+
...baseProps,
|
|
840
|
+
fieldType,
|
|
841
|
+
value: baseProps.value,
|
|
842
|
+
onChange: baseProps.onChange,
|
|
843
|
+
...constraints
|
|
844
|
+
};
|
|
845
|
+
} else if (fieldType === "select" || fieldType === "multiselect") {
|
|
846
|
+
const selectOptions = isSelectionField(fieldDef) ? fieldDef.options : [];
|
|
847
|
+
fieldProps = {
|
|
848
|
+
...baseProps,
|
|
849
|
+
fieldType,
|
|
850
|
+
value: baseProps.value,
|
|
851
|
+
onChange: baseProps.onChange,
|
|
852
|
+
options: formaOptionsVisibility[fieldPath] ?? selectOptions ?? []
|
|
853
|
+
};
|
|
854
|
+
} else if (fieldType === "array" && fieldDef.type === "array" && fieldDef.itemFields) {
|
|
855
|
+
const arrayValue = Array.isArray(baseProps.value) ? baseProps.value : [];
|
|
856
|
+
const minItems = fieldDef.minItems ?? 0;
|
|
857
|
+
const maxItems = fieldDef.maxItems ?? Infinity;
|
|
858
|
+
const itemFieldDefs = fieldDef.itemFields;
|
|
859
|
+
const baseHelpers = getArrayHelpers(fieldPath);
|
|
860
|
+
const pushWithDefault = (item) => {
|
|
861
|
+
const newItem = item ?? createDefaultItem(itemFieldDefs);
|
|
862
|
+
baseHelpers.push(newItem);
|
|
863
|
+
};
|
|
864
|
+
const getItemFieldPropsExtended = (index, fieldName) => {
|
|
865
|
+
const baseProps2 = baseHelpers.getItemFieldProps(index, fieldName);
|
|
866
|
+
const itemFieldDef = itemFieldDefs[fieldName];
|
|
867
|
+
const itemPath = `${fieldPath}[${index}].${fieldName}`;
|
|
868
|
+
return {
|
|
869
|
+
...baseProps2,
|
|
870
|
+
itemIndex: index,
|
|
871
|
+
fieldName,
|
|
872
|
+
options: formaOptionsVisibility[itemPath] ?? (itemFieldDef && isSelectionField(itemFieldDef) ? itemFieldDef.options : void 0)
|
|
873
|
+
};
|
|
874
|
+
};
|
|
875
|
+
const helpers = {
|
|
876
|
+
items: arrayValue,
|
|
877
|
+
push: pushWithDefault,
|
|
878
|
+
insert: baseHelpers.insert,
|
|
879
|
+
remove: baseHelpers.remove,
|
|
880
|
+
move: baseHelpers.move,
|
|
881
|
+
swap: baseHelpers.swap,
|
|
882
|
+
getItemFieldProps: getItemFieldPropsExtended,
|
|
883
|
+
minItems,
|
|
884
|
+
maxItems,
|
|
885
|
+
canAdd: arrayValue.length < maxItems,
|
|
886
|
+
canRemove: arrayValue.length > minItems
|
|
887
|
+
};
|
|
888
|
+
fieldProps = {
|
|
889
|
+
...baseProps,
|
|
890
|
+
fieldType: "array",
|
|
891
|
+
value: arrayValue,
|
|
892
|
+
onChange: baseProps.onChange,
|
|
893
|
+
helpers,
|
|
894
|
+
itemFields: itemFieldDefs,
|
|
895
|
+
minItems,
|
|
896
|
+
maxItems
|
|
897
|
+
};
|
|
898
|
+
} else if (fieldType === "display" && fieldDef.type === "display") {
|
|
899
|
+
const sourceValue = fieldDef.source ? formaData[fieldDef.source] ?? formaComputed[fieldDef.source] : void 0;
|
|
900
|
+
const {
|
|
901
|
+
onChange: _onChange,
|
|
902
|
+
value: _value,
|
|
903
|
+
...displayBaseProps
|
|
904
|
+
} = baseProps;
|
|
905
|
+
fieldProps = {
|
|
906
|
+
...displayBaseProps,
|
|
907
|
+
fieldType: "display",
|
|
908
|
+
content: fieldDef.content,
|
|
909
|
+
sourceValue,
|
|
910
|
+
format: fieldDef.format
|
|
911
|
+
};
|
|
912
|
+
} else {
|
|
913
|
+
fieldProps = {
|
|
914
|
+
...baseProps,
|
|
915
|
+
fieldType,
|
|
916
|
+
value: baseProps.value ?? "",
|
|
917
|
+
onChange: baseProps.onChange
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
const componentProps = { field: fieldProps, spec };
|
|
921
|
+
return /* @__PURE__ */ jsx("div", { "data-field-path": fieldPath, children: /* @__PURE__ */ jsx(
|
|
922
|
+
FieldWrapper,
|
|
923
|
+
{
|
|
924
|
+
fieldPath,
|
|
925
|
+
field: fieldDef,
|
|
926
|
+
errors,
|
|
927
|
+
touched,
|
|
928
|
+
required,
|
|
929
|
+
showRequiredIndicator,
|
|
930
|
+
visible: isVisible,
|
|
931
|
+
children: React.createElement(
|
|
932
|
+
Component,
|
|
933
|
+
componentProps
|
|
934
|
+
)
|
|
935
|
+
}
|
|
936
|
+
) }, fieldPath);
|
|
937
|
+
},
|
|
938
|
+
[
|
|
939
|
+
spec,
|
|
940
|
+
components,
|
|
941
|
+
FieldWrapper,
|
|
942
|
+
formaData,
|
|
943
|
+
formaComputed,
|
|
944
|
+
formaVisibility,
|
|
945
|
+
formaRequired,
|
|
946
|
+
formaEnabled,
|
|
947
|
+
formaReadonly,
|
|
948
|
+
formaOptionsVisibility,
|
|
949
|
+
formaTouched,
|
|
950
|
+
formaErrors,
|
|
951
|
+
setFieldValue,
|
|
952
|
+
setFieldTouched,
|
|
953
|
+
getArrayHelpers
|
|
954
|
+
]
|
|
955
|
+
);
|
|
802
956
|
const renderedFields = useMemo2(
|
|
803
957
|
() => fieldsToRender.map(renderField),
|
|
804
958
|
[fieldsToRender, renderField]
|
|
@@ -862,7 +1016,11 @@ function createDefaultItem2(itemFields) {
|
|
|
862
1016
|
}
|
|
863
1017
|
return item;
|
|
864
1018
|
}
|
|
865
|
-
function FieldRenderer({
|
|
1019
|
+
function FieldRenderer({
|
|
1020
|
+
fieldPath,
|
|
1021
|
+
components,
|
|
1022
|
+
className
|
|
1023
|
+
}) {
|
|
866
1024
|
const forma = useFormaContext();
|
|
867
1025
|
const { spec } = forma;
|
|
868
1026
|
const fieldDef = spec.fields[fieldPath];
|
|
@@ -871,7 +1029,9 @@ function FieldRenderer({ fieldPath, components, className }) {
|
|
|
871
1029
|
return null;
|
|
872
1030
|
}
|
|
873
1031
|
const isVisible = forma.visibility[fieldPath] !== false;
|
|
874
|
-
if (!isVisible)
|
|
1032
|
+
if (!isVisible) {
|
|
1033
|
+
return /* @__PURE__ */ jsx2("div", { "data-field-path": fieldPath, hidden: true });
|
|
1034
|
+
}
|
|
875
1035
|
const fieldType = fieldDef.type;
|
|
876
1036
|
const componentKey = fieldType;
|
|
877
1037
|
const Component = components[componentKey] || components.fallback;
|
|
@@ -979,7 +1139,10 @@ function FieldRenderer({ fieldPath, components, className }) {
|
|
|
979
1139
|
},
|
|
980
1140
|
swap: (indexA, indexB) => {
|
|
981
1141
|
const newArray = [...arrayValue];
|
|
982
|
-
[newArray[indexA], newArray[indexB]] = [
|
|
1142
|
+
[newArray[indexA], newArray[indexB]] = [
|
|
1143
|
+
newArray[indexB],
|
|
1144
|
+
newArray[indexA]
|
|
1145
|
+
];
|
|
983
1146
|
forma.setFieldValue(fieldPath, newArray);
|
|
984
1147
|
},
|
|
985
1148
|
getItemFieldProps: (index, fieldName) => {
|
|
@@ -1030,7 +1193,11 @@ function FieldRenderer({ fieldPath, components, className }) {
|
|
|
1030
1193
|
};
|
|
1031
1194
|
} else if (fieldType === "display" && fieldDef.type === "display") {
|
|
1032
1195
|
const sourceValue = fieldDef.source ? forma.data[fieldDef.source] ?? forma.computed[fieldDef.source] : void 0;
|
|
1033
|
-
const {
|
|
1196
|
+
const {
|
|
1197
|
+
onChange: _onChange,
|
|
1198
|
+
value: _value,
|
|
1199
|
+
...displayBaseProps
|
|
1200
|
+
} = baseProps;
|
|
1034
1201
|
fieldProps = {
|
|
1035
1202
|
...displayBaseProps,
|
|
1036
1203
|
fieldType: "display",
|
|
@@ -1047,11 +1214,14 @@ function FieldRenderer({ fieldPath, components, className }) {
|
|
|
1047
1214
|
};
|
|
1048
1215
|
}
|
|
1049
1216
|
const componentProps = { field: fieldProps, spec };
|
|
1050
|
-
const element = React2.createElement(
|
|
1217
|
+
const element = React2.createElement(
|
|
1218
|
+
Component,
|
|
1219
|
+
componentProps
|
|
1220
|
+
);
|
|
1051
1221
|
if (className) {
|
|
1052
|
-
return /* @__PURE__ */ jsx2("div", { className, children: element });
|
|
1222
|
+
return /* @__PURE__ */ jsx2("div", { "data-field-path": fieldPath, className, children: element });
|
|
1053
1223
|
}
|
|
1054
|
-
return element;
|
|
1224
|
+
return /* @__PURE__ */ jsx2("div", { "data-field-path": fieldPath, children: element });
|
|
1055
1225
|
}
|
|
1056
1226
|
|
|
1057
1227
|
// src/ErrorBoundary.tsx
|