@fogpipe/forma-react 0.11.1 → 0.12.0-alpha.1
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 +44 -2
- package/dist/index.js +76 -15
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
- package/src/FieldRenderer.tsx +29 -4
- package/src/FormRenderer.tsx +34 -8
- package/src/__tests__/FormRenderer.test.tsx +186 -0
- package/src/index.ts +2 -0
- package/src/types.ts +44 -1
- package/src/useForma.ts +30 -6
package/dist/index.d.ts
CHANGED
|
@@ -34,12 +34,22 @@ interface BaseFieldProps {
|
|
|
34
34
|
visible: boolean;
|
|
35
35
|
/** Whether field is enabled (inverse of disabled) */
|
|
36
36
|
enabled: boolean;
|
|
37
|
+
/** Whether field is readonly (visible, not editable, value still submitted) */
|
|
38
|
+
readonly: boolean;
|
|
37
39
|
/** Display label from field definition */
|
|
38
40
|
label: string;
|
|
39
41
|
/** Help text or description from field definition */
|
|
40
42
|
description?: string;
|
|
41
43
|
/** Placeholder text from field definition */
|
|
42
44
|
placeholder?: string;
|
|
45
|
+
/** Prefix adorner text (e.g., "$") - only for adornable field types */
|
|
46
|
+
prefix?: string;
|
|
47
|
+
/** Suffix adorner text (e.g., "kg") - only for adornable field types */
|
|
48
|
+
suffix?: string;
|
|
49
|
+
/** Presentation variant hint (e.g., "slider", "radio", "nps") */
|
|
50
|
+
variant?: string;
|
|
51
|
+
/** Variant-specific configuration */
|
|
52
|
+
variantConfig?: Record<string, unknown>;
|
|
43
53
|
}
|
|
44
54
|
/**
|
|
45
55
|
* Props for text-based fields (text, email, password, url, textarea)
|
|
@@ -226,10 +236,25 @@ interface ArrayItemFieldProps extends Omit<BaseFieldProps, "value" | "onChange">
|
|
|
226
236
|
/** Field name within the item */
|
|
227
237
|
fieldName: string;
|
|
228
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Props for display fields (read-only presentation content)
|
|
241
|
+
*/
|
|
242
|
+
interface DisplayFieldProps extends Omit<BaseFieldProps, "value" | "onChange"> {
|
|
243
|
+
fieldType: "display";
|
|
244
|
+
/** Static content (markdown/text) */
|
|
245
|
+
content?: string;
|
|
246
|
+
/** Computed source value (resolved by useForma from display field's source property) */
|
|
247
|
+
sourceValue?: unknown;
|
|
248
|
+
/** Display format string */
|
|
249
|
+
format?: string;
|
|
250
|
+
/** No onChange - display fields are read-only */
|
|
251
|
+
onChange?: never;
|
|
252
|
+
value?: never;
|
|
253
|
+
}
|
|
229
254
|
/**
|
|
230
255
|
* Union of all field prop types
|
|
231
256
|
*/
|
|
232
|
-
type FieldProps = TextFieldProps | NumberFieldProps | IntegerFieldProps | BooleanFieldProps | DateFieldProps | DateTimeFieldProps | SelectFieldProps | MultiSelectFieldProps | ArrayFieldProps | ObjectFieldProps | ComputedFieldProps;
|
|
257
|
+
type FieldProps = TextFieldProps | NumberFieldProps | IntegerFieldProps | BooleanFieldProps | DateFieldProps | DateTimeFieldProps | SelectFieldProps | MultiSelectFieldProps | ArrayFieldProps | ObjectFieldProps | ComputedFieldProps | DisplayFieldProps;
|
|
233
258
|
/**
|
|
234
259
|
* Map of field types to React components
|
|
235
260
|
* Components receive wrapper props with { field, spec } structure
|
|
@@ -250,6 +275,7 @@ interface ComponentMap {
|
|
|
250
275
|
array?: React.ComponentType<ArrayComponentProps>;
|
|
251
276
|
object?: React.ComponentType<ObjectComponentProps>;
|
|
252
277
|
computed?: React.ComponentType<ComputedComponentProps>;
|
|
278
|
+
display?: React.ComponentType<DisplayComponentProps>;
|
|
253
279
|
fallback?: React.ComponentType<FieldComponentProps>;
|
|
254
280
|
}
|
|
255
281
|
/**
|
|
@@ -338,6 +364,10 @@ interface ComputedComponentProps {
|
|
|
338
364
|
field: ComputedFieldProps;
|
|
339
365
|
spec: Forma;
|
|
340
366
|
}
|
|
367
|
+
interface DisplayComponentProps {
|
|
368
|
+
field: DisplayFieldProps;
|
|
369
|
+
spec: Forma;
|
|
370
|
+
}
|
|
341
371
|
/**
|
|
342
372
|
* Generic field component props (for fallback/dynamic components)
|
|
343
373
|
*/
|
|
@@ -367,6 +397,8 @@ interface GetFieldPropsResult {
|
|
|
367
397
|
visible: boolean;
|
|
368
398
|
/** Whether field is enabled (not disabled) */
|
|
369
399
|
enabled: boolean;
|
|
400
|
+
/** Whether field is readonly (visible, not editable, value still submitted) */
|
|
401
|
+
readonly: boolean;
|
|
370
402
|
/** Whether field is required (for validation) */
|
|
371
403
|
required: boolean;
|
|
372
404
|
/**
|
|
@@ -390,6 +422,14 @@ interface GetFieldPropsResult {
|
|
|
390
422
|
"aria-required"?: boolean;
|
|
391
423
|
/** Options for select/multiselect fields (filtered by visibleWhen) */
|
|
392
424
|
options?: SelectOption[];
|
|
425
|
+
/** Prefix adorner text (e.g., "$") */
|
|
426
|
+
prefix?: string;
|
|
427
|
+
/** Suffix adorner text (e.g., "kg") */
|
|
428
|
+
suffix?: string;
|
|
429
|
+
/** Presentation variant hint */
|
|
430
|
+
variant?: string;
|
|
431
|
+
/** Variant-specific configuration */
|
|
432
|
+
variantConfig?: Record<string, unknown>;
|
|
393
433
|
}
|
|
394
434
|
/**
|
|
395
435
|
* Select field props returned by getSelectFieldProps()
|
|
@@ -501,6 +541,8 @@ interface UseFormaReturn {
|
|
|
501
541
|
required: Record<string, boolean>;
|
|
502
542
|
/** Field enabled state map */
|
|
503
543
|
enabled: Record<string, boolean>;
|
|
544
|
+
/** Field readonly state map */
|
|
545
|
+
readonly: Record<string, boolean>;
|
|
504
546
|
/** Visible options for select/multiselect fields, keyed by field path */
|
|
505
547
|
optionsVisibility: OptionsVisibilityResult;
|
|
506
548
|
/** Field touched state map */
|
|
@@ -680,4 +722,4 @@ declare const FormaContext: React$1.Context<UseFormaReturn | null>;
|
|
|
680
722
|
*/
|
|
681
723
|
declare function useFormaContext(): UseFormaReturn;
|
|
682
724
|
|
|
683
|
-
export { type ArrayComponentProps, type ArrayFieldProps, type ArrayHelpers, type ArrayItemFieldProps, type ArrayItemFieldPropsResult, type BaseFieldProps, type BooleanComponentProps, type BooleanFieldProps, type ComponentMap, type ComputedComponentProps, type ComputedFieldProps, type DateComponentProps, type DateFieldProps, type DateTimeComponentProps, type DateTimeFieldProps, type FieldComponentProps, type FieldProps, FieldRenderer, type FieldRendererProps, type FieldWrapperProps, FormRenderer, type FormRendererHandle, type FormRendererProps, type UseFormaReturn as FormState, FormaContext, FormaErrorBoundary, type FormaErrorBoundaryProps, type GetArrayHelpersResult, type GetFieldPropsResult, type GetSelectFieldPropsResult, type IntegerComponentProps, type IntegerFieldProps, type LayoutProps, type LegacyFieldProps, type MultiSelectComponentProps, type MultiSelectFieldProps, type NumberComponentProps, type NumberFieldProps, type ObjectComponentProps, type ObjectFieldProps, type PageState, type PageWrapperProps, type SelectComponentProps, type SelectFieldProps, type SelectionFieldProps, type TextComponentProps, type TextFieldProps, type UseFormaOptions, type UseFormaReturn, type WizardHelpers, useForma, useFormaContext };
|
|
725
|
+
export { type ArrayComponentProps, type ArrayFieldProps, type ArrayHelpers, type ArrayItemFieldProps, type ArrayItemFieldPropsResult, type BaseFieldProps, type BooleanComponentProps, type BooleanFieldProps, type ComponentMap, type ComputedComponentProps, type ComputedFieldProps, type DateComponentProps, type DateFieldProps, type DateTimeComponentProps, type DateTimeFieldProps, type DisplayComponentProps, type DisplayFieldProps, type FieldComponentProps, type FieldProps, FieldRenderer, type FieldRendererProps, type FieldWrapperProps, FormRenderer, type FormRendererHandle, type FormRendererProps, type UseFormaReturn as FormState, FormaContext, FormaErrorBoundary, type FormaErrorBoundaryProps, type GetArrayHelpersResult, type GetFieldPropsResult, type GetSelectFieldPropsResult, type IntegerComponentProps, type IntegerFieldProps, type LayoutProps, type LegacyFieldProps, type MultiSelectComponentProps, type MultiSelectFieldProps, type NumberComponentProps, type NumberFieldProps, type ObjectComponentProps, type ObjectFieldProps, type PageState, type PageWrapperProps, type SelectComponentProps, type SelectFieldProps, type SelectionFieldProps, type TextComponentProps, type TextFieldProps, type UseFormaOptions, type UseFormaReturn, type WizardHelpers, useForma, useFormaContext };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// src/useForma.ts
|
|
2
2
|
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
3
|
+
import { isAdornableField } from "@fogpipe/forma-core";
|
|
3
4
|
import {
|
|
4
5
|
getVisibility,
|
|
5
6
|
getRequired,
|
|
6
7
|
getEnabled,
|
|
8
|
+
getReadonly,
|
|
7
9
|
validate,
|
|
8
10
|
calculate,
|
|
9
11
|
getPageVisibility,
|
|
@@ -103,6 +105,10 @@ function useForma(options) {
|
|
|
103
105
|
() => getEnabled(state.data, spec, { computed }),
|
|
104
106
|
[state.data, spec, computed]
|
|
105
107
|
);
|
|
108
|
+
const readonly = useMemo(
|
|
109
|
+
() => getReadonly(state.data, spec, { computed }),
|
|
110
|
+
[state.data, spec, computed]
|
|
111
|
+
);
|
|
106
112
|
const optionsVisibility = useMemo(
|
|
107
113
|
() => getOptionsVisibility(state.data, spec, { computed }),
|
|
108
114
|
[state.data, spec, computed]
|
|
@@ -300,7 +306,7 @@ function useForma(options) {
|
|
|
300
306
|
const validFields = new Set(spec.fieldOrder);
|
|
301
307
|
for (const fieldId of spec.fieldOrder) {
|
|
302
308
|
const fieldDef = spec.fields[fieldId];
|
|
303
|
-
if (fieldDef == null ? void 0 : fieldDef.itemFields) {
|
|
309
|
+
if ((fieldDef == null ? void 0 : fieldDef.type) === "array" && fieldDef.itemFields) {
|
|
304
310
|
for (const key of fieldHandlers.current.keys()) {
|
|
305
311
|
if (key.startsWith(`${fieldId}[`)) {
|
|
306
312
|
validFields.add(key);
|
|
@@ -356,6 +362,7 @@ function useForma(options) {
|
|
|
356
362
|
const isBooleanField = (schemaProperty == null ? void 0 : schemaProperty.type) === "boolean" || (fieldDef == null ? void 0 : fieldDef.type) === "boolean";
|
|
357
363
|
const hasValidationRules = (((_a = fieldDef == null ? void 0 : fieldDef.validations) == null ? void 0 : _a.length) ?? 0) > 0;
|
|
358
364
|
const showRequiredIndicator = isRequired && (!isBooleanField || hasValidationRules);
|
|
365
|
+
const adornerProps = fieldDef && isAdornableField(fieldDef) ? { prefix: fieldDef.prefix, suffix: fieldDef.suffix } : {};
|
|
359
366
|
return {
|
|
360
367
|
name: path,
|
|
361
368
|
value: getValueAtPath(path),
|
|
@@ -365,6 +372,7 @@ function useForma(options) {
|
|
|
365
372
|
placeholder: fieldDef == null ? void 0 : fieldDef.placeholder,
|
|
366
373
|
visible: visibility[path] !== false,
|
|
367
374
|
enabled: enabled[path] !== false,
|
|
375
|
+
readonly: readonly[path] ?? false,
|
|
368
376
|
required: isRequired,
|
|
369
377
|
showRequiredIndicator,
|
|
370
378
|
touched: isTouched,
|
|
@@ -374,9 +382,14 @@ function useForma(options) {
|
|
|
374
382
|
// ARIA accessibility attributes
|
|
375
383
|
"aria-invalid": hasErrors || void 0,
|
|
376
384
|
"aria-describedby": hasErrors ? `${path}-error` : void 0,
|
|
377
|
-
"aria-required": isRequired || 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
|
|
378
391
|
};
|
|
379
|
-
}, [spec, state.touched, state.isSubmitted, visibility, enabled, required, validation.errors, validateOn, getValueAtPath, getFieldHandlers]);
|
|
392
|
+
}, [spec, state.touched, state.isSubmitted, visibility, enabled, readonly, required, validation.errors, validateOn, getValueAtPath, getFieldHandlers]);
|
|
380
393
|
const getSelectFieldProps = useCallback((path) => {
|
|
381
394
|
const baseProps = getFieldProps(path);
|
|
382
395
|
const visibleOptions = optionsVisibility[path] ?? [];
|
|
@@ -388,14 +401,15 @@ function useForma(options) {
|
|
|
388
401
|
const getArrayHelpers = useCallback((path) => {
|
|
389
402
|
const fieldDef = spec.fields[path];
|
|
390
403
|
const currentValue = getValueAtPath(path) ?? [];
|
|
391
|
-
const
|
|
392
|
-
const
|
|
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;
|
|
393
407
|
const canAdd = currentValue.length < maxItems;
|
|
394
408
|
const canRemove = currentValue.length > minItems;
|
|
395
409
|
const getItemFieldProps = (index, fieldName) => {
|
|
396
410
|
var _a;
|
|
397
411
|
const itemPath = `${path}[${index}].${fieldName}`;
|
|
398
|
-
const itemFieldDef = (_a =
|
|
412
|
+
const itemFieldDef = (_a = arrayDef == null ? void 0 : arrayDef.itemFields) == null ? void 0 : _a[fieldName];
|
|
399
413
|
const handlers = getFieldHandlers(itemPath);
|
|
400
414
|
const item = currentValue[index] ?? {};
|
|
401
415
|
const itemValue = item[fieldName];
|
|
@@ -412,6 +426,7 @@ function useForma(options) {
|
|
|
412
426
|
placeholder: itemFieldDef == null ? void 0 : itemFieldDef.placeholder,
|
|
413
427
|
visible: true,
|
|
414
428
|
enabled: enabled[path] !== false,
|
|
429
|
+
readonly: readonly[itemPath] ?? false,
|
|
415
430
|
required: false,
|
|
416
431
|
// TODO: Evaluate item field required
|
|
417
432
|
showRequiredIndicator: false,
|
|
@@ -461,13 +476,14 @@ function useForma(options) {
|
|
|
461
476
|
canAdd,
|
|
462
477
|
canRemove
|
|
463
478
|
};
|
|
464
|
-
}, [spec.fields, getValueAtPath, setValueAtPath, getFieldHandlers, enabled, state.touched, state.isSubmitted, validation.errors, validateOn, optionsVisibility]);
|
|
479
|
+
}, [spec.fields, getValueAtPath, setValueAtPath, getFieldHandlers, enabled, readonly, state.touched, state.isSubmitted, validation.errors, validateOn, optionsVisibility]);
|
|
465
480
|
return {
|
|
466
481
|
data: state.data,
|
|
467
482
|
computed,
|
|
468
483
|
visibility,
|
|
469
484
|
required,
|
|
470
485
|
enabled,
|
|
486
|
+
readonly,
|
|
471
487
|
optionsVisibility,
|
|
472
488
|
touched: state.touched,
|
|
473
489
|
errors: validation.errors,
|
|
@@ -492,6 +508,7 @@ function useForma(options) {
|
|
|
492
508
|
|
|
493
509
|
// src/FormRenderer.tsx
|
|
494
510
|
import React, { forwardRef, useImperativeHandle, useRef as useRef2, useMemo as useMemo2, useCallback as useCallback2 } from "react";
|
|
511
|
+
import { isAdornableField as isAdornableField2, isSelectionField } from "@fogpipe/forma-core";
|
|
495
512
|
|
|
496
513
|
// src/context.ts
|
|
497
514
|
import { createContext, useContext } from "react";
|
|
@@ -642,7 +659,7 @@ var FormRenderer = forwardRef(
|
|
|
642
659
|
if (!fieldDef) return null;
|
|
643
660
|
const isVisible = forma.visibility[fieldPath] !== false;
|
|
644
661
|
if (!isVisible) return null;
|
|
645
|
-
const fieldType = fieldDef.type
|
|
662
|
+
const fieldType = fieldDef.type;
|
|
646
663
|
const componentKey = fieldType;
|
|
647
664
|
const Component = components[componentKey] || components.fallback;
|
|
648
665
|
if (!Component) {
|
|
@@ -657,6 +674,7 @@ var FormRenderer = forwardRef(
|
|
|
657
674
|
const isBooleanField = (schemaProperty == null ? void 0 : schemaProperty.type) === "boolean" || (fieldDef == null ? void 0 : fieldDef.type) === "boolean";
|
|
658
675
|
const hasValidationRules = (((_a = fieldDef == null ? void 0 : fieldDef.validations) == null ? void 0 : _a.length) ?? 0) > 0;
|
|
659
676
|
const showRequiredIndicator = required && (!isBooleanField || hasValidationRules);
|
|
677
|
+
const isReadonly = forma.readonly[fieldPath] ?? false;
|
|
660
678
|
const baseProps = {
|
|
661
679
|
name: fieldPath,
|
|
662
680
|
field: fieldDef,
|
|
@@ -671,9 +689,18 @@ var FormRenderer = forwardRef(
|
|
|
671
689
|
visible: true,
|
|
672
690
|
// Always true since we already filtered for visibility
|
|
673
691
|
enabled: !disabled,
|
|
692
|
+
readonly: isReadonly,
|
|
674
693
|
label: fieldDef.label ?? fieldPath,
|
|
675
694
|
description: fieldDef.description,
|
|
676
|
-
placeholder: fieldDef.placeholder
|
|
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
|
|
677
704
|
};
|
|
678
705
|
let fieldProps = baseProps;
|
|
679
706
|
if (fieldType === "number" || fieldType === "integer") {
|
|
@@ -686,14 +713,15 @@ var FormRenderer = forwardRef(
|
|
|
686
713
|
...constraints
|
|
687
714
|
};
|
|
688
715
|
} else if (fieldType === "select" || fieldType === "multiselect") {
|
|
716
|
+
const selectOptions = isSelectionField(fieldDef) ? fieldDef.options : [];
|
|
689
717
|
fieldProps = {
|
|
690
718
|
...baseProps,
|
|
691
719
|
fieldType,
|
|
692
720
|
value: baseProps.value,
|
|
693
721
|
onChange: baseProps.onChange,
|
|
694
|
-
options:
|
|
722
|
+
options: forma.optionsVisibility[fieldPath] ?? selectOptions ?? []
|
|
695
723
|
};
|
|
696
|
-
} else if (fieldType === "array" && fieldDef.itemFields) {
|
|
724
|
+
} else if (fieldType === "array" && fieldDef.type === "array" && fieldDef.itemFields) {
|
|
697
725
|
const arrayValue = Array.isArray(baseProps.value) ? baseProps.value : [];
|
|
698
726
|
const minItems = fieldDef.minItems ?? 0;
|
|
699
727
|
const maxItems = fieldDef.maxItems ?? Infinity;
|
|
@@ -706,11 +734,12 @@ var FormRenderer = forwardRef(
|
|
|
706
734
|
const getItemFieldPropsExtended = (index, fieldName) => {
|
|
707
735
|
const baseProps2 = baseHelpers.getItemFieldProps(index, fieldName);
|
|
708
736
|
const itemFieldDef = itemFieldDefs[fieldName];
|
|
737
|
+
const itemPath = `${fieldPath}[${index}].${fieldName}`;
|
|
709
738
|
return {
|
|
710
739
|
...baseProps2,
|
|
711
740
|
itemIndex: index,
|
|
712
741
|
fieldName,
|
|
713
|
-
options: itemFieldDef
|
|
742
|
+
options: forma.optionsVisibility[itemPath] ?? (itemFieldDef && isSelectionField(itemFieldDef) ? itemFieldDef.options : void 0)
|
|
714
743
|
};
|
|
715
744
|
};
|
|
716
745
|
const helpers = {
|
|
@@ -736,6 +765,16 @@ var FormRenderer = forwardRef(
|
|
|
736
765
|
minItems,
|
|
737
766
|
maxItems
|
|
738
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
|
+
};
|
|
739
778
|
} else {
|
|
740
779
|
fieldProps = {
|
|
741
780
|
...baseProps,
|
|
@@ -795,6 +834,7 @@ var FormRenderer = forwardRef(
|
|
|
795
834
|
|
|
796
835
|
// src/FieldRenderer.tsx
|
|
797
836
|
import React2 from "react";
|
|
837
|
+
import { isAdornableField as isAdornableField3 } from "@fogpipe/forma-core";
|
|
798
838
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
799
839
|
function getNumberConstraints2(schema) {
|
|
800
840
|
if (!schema) return {};
|
|
@@ -832,7 +872,7 @@ function FieldRenderer({ fieldPath, components, className }) {
|
|
|
832
872
|
}
|
|
833
873
|
const isVisible = forma.visibility[fieldPath] !== false;
|
|
834
874
|
if (!isVisible) return null;
|
|
835
|
-
const fieldType = fieldDef.type
|
|
875
|
+
const fieldType = fieldDef.type;
|
|
836
876
|
const componentKey = fieldType;
|
|
837
877
|
const Component = components[componentKey] || components.fallback;
|
|
838
878
|
if (!Component) {
|
|
@@ -844,6 +884,7 @@ function FieldRenderer({ fieldPath, components, className }) {
|
|
|
844
884
|
const required = forma.required[fieldPath] ?? false;
|
|
845
885
|
const disabled = forma.enabled[fieldPath] === false;
|
|
846
886
|
const schemaProperty = spec.schema.properties[fieldPath];
|
|
887
|
+
const isReadonly = forma.readonly[fieldPath] ?? false;
|
|
847
888
|
const baseProps = {
|
|
848
889
|
name: fieldPath,
|
|
849
890
|
field: fieldDef,
|
|
@@ -858,9 +899,18 @@ function FieldRenderer({ fieldPath, components, className }) {
|
|
|
858
899
|
visible: true,
|
|
859
900
|
// Always true since we already filtered for visibility
|
|
860
901
|
enabled: !disabled,
|
|
902
|
+
readonly: isReadonly,
|
|
861
903
|
label: fieldDef.label ?? fieldPath,
|
|
862
904
|
description: fieldDef.description,
|
|
863
|
-
placeholder: fieldDef.placeholder
|
|
905
|
+
placeholder: fieldDef.placeholder,
|
|
906
|
+
// Adorner properties (only for adornable field types)
|
|
907
|
+
...isAdornableField3(fieldDef) && {
|
|
908
|
+
prefix: fieldDef.prefix,
|
|
909
|
+
suffix: fieldDef.suffix
|
|
910
|
+
},
|
|
911
|
+
// Presentation variant
|
|
912
|
+
variant: fieldDef.variant,
|
|
913
|
+
variantConfig: fieldDef.variantConfig
|
|
864
914
|
};
|
|
865
915
|
let fieldProps = baseProps;
|
|
866
916
|
if (fieldType === "number") {
|
|
@@ -900,7 +950,7 @@ function FieldRenderer({ fieldPath, components, className }) {
|
|
|
900
950
|
onChange: baseProps.onChange,
|
|
901
951
|
options: visibleOptions
|
|
902
952
|
};
|
|
903
|
-
} else if (fieldType === "array" && fieldDef.itemFields) {
|
|
953
|
+
} else if (fieldType === "array" && fieldDef.type === "array" && fieldDef.itemFields) {
|
|
904
954
|
const arrayValue = baseProps.value ?? [];
|
|
905
955
|
const minItems = fieldDef.minItems ?? 0;
|
|
906
956
|
const maxItems = fieldDef.maxItems ?? Infinity;
|
|
@@ -947,6 +997,7 @@ function FieldRenderer({ fieldPath, components, className }) {
|
|
|
947
997
|
placeholder: itemFieldDef == null ? void 0 : itemFieldDef.placeholder,
|
|
948
998
|
visible: true,
|
|
949
999
|
enabled: !disabled,
|
|
1000
|
+
readonly: forma.readonly[itemPath] ?? false,
|
|
950
1001
|
required: (itemFieldDef == null ? void 0 : itemFieldDef.requiredWhen) === "true",
|
|
951
1002
|
touched: forma.touched[itemPath] ?? false,
|
|
952
1003
|
errors: forma.errors.filter((e) => e.field === itemPath),
|
|
@@ -977,6 +1028,16 @@ function FieldRenderer({ fieldPath, components, className }) {
|
|
|
977
1028
|
minItems,
|
|
978
1029
|
maxItems
|
|
979
1030
|
};
|
|
1031
|
+
} else if (fieldType === "display" && fieldDef.type === "display") {
|
|
1032
|
+
const sourceValue = fieldDef.source ? forma.data[fieldDef.source] ?? forma.computed[fieldDef.source] : void 0;
|
|
1033
|
+
const { onChange: _onChange, value: _value, ...displayBaseProps } = baseProps;
|
|
1034
|
+
fieldProps = {
|
|
1035
|
+
...displayBaseProps,
|
|
1036
|
+
fieldType: "display",
|
|
1037
|
+
content: fieldDef.content,
|
|
1038
|
+
sourceValue,
|
|
1039
|
+
format: fieldDef.format
|
|
1040
|
+
};
|
|
980
1041
|
} else {
|
|
981
1042
|
fieldProps = {
|
|
982
1043
|
...baseProps,
|