@fogpipe/forma-react 0.12.0 → 0.13.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 +75 -61
- package/dist/index.js +36 -8
- 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__/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/index.ts +6 -1
- package/src/types.ts +58 -14
- package/src/useForma.ts +31 -3
package/src/context.ts
CHANGED
|
@@ -17,7 +17,9 @@ export const FormaContext = createContext<UseFormaReturn | null>(null);
|
|
|
17
17
|
export function useFormaContext(): UseFormaReturn {
|
|
18
18
|
const context = useContext(FormaContext);
|
|
19
19
|
if (!context) {
|
|
20
|
-
throw new Error(
|
|
20
|
+
throw new Error(
|
|
21
|
+
"useFormaContext must be used within a FormaContext.Provider",
|
|
22
|
+
);
|
|
21
23
|
}
|
|
22
24
|
return context;
|
|
23
25
|
}
|
package/src/index.ts
CHANGED
|
@@ -20,7 +20,12 @@ export type {
|
|
|
20
20
|
|
|
21
21
|
// Hook
|
|
22
22
|
export { useForma } from "./useForma.js";
|
|
23
|
-
export type {
|
|
23
|
+
export type {
|
|
24
|
+
UseFormaOptions,
|
|
25
|
+
UseFormaReturn,
|
|
26
|
+
PageState,
|
|
27
|
+
WizardHelpers,
|
|
28
|
+
} from "./useForma.js";
|
|
24
29
|
|
|
25
30
|
// Components
|
|
26
31
|
export { FormRenderer } from "./FormRenderer.js";
|
package/src/types.ts
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
* Type definitions for forma-react components
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
Forma,
|
|
7
|
+
FieldDefinition,
|
|
8
|
+
FieldError,
|
|
9
|
+
SelectOption,
|
|
10
|
+
} from "@fogpipe/forma-core";
|
|
6
11
|
|
|
7
12
|
/**
|
|
8
13
|
* Base props shared by all field components
|
|
@@ -52,7 +57,10 @@ export interface BaseFieldProps {
|
|
|
52
57
|
/**
|
|
53
58
|
* Props for text-based fields (text, email, password, url, textarea)
|
|
54
59
|
*/
|
|
55
|
-
export interface TextFieldProps extends Omit<
|
|
60
|
+
export interface TextFieldProps extends Omit<
|
|
61
|
+
BaseFieldProps,
|
|
62
|
+
"value" | "onChange"
|
|
63
|
+
> {
|
|
56
64
|
fieldType: "text" | "email" | "password" | "url" | "textarea";
|
|
57
65
|
value: string;
|
|
58
66
|
onChange: (value: string) => void;
|
|
@@ -61,7 +69,10 @@ export interface TextFieldProps extends Omit<BaseFieldProps, "value" | "onChange
|
|
|
61
69
|
/**
|
|
62
70
|
* Props for number fields
|
|
63
71
|
*/
|
|
64
|
-
export interface NumberFieldProps extends Omit<
|
|
72
|
+
export interface NumberFieldProps extends Omit<
|
|
73
|
+
BaseFieldProps,
|
|
74
|
+
"value" | "onChange"
|
|
75
|
+
> {
|
|
65
76
|
fieldType: "number";
|
|
66
77
|
value: number | null;
|
|
67
78
|
onChange: (value: number | null) => void;
|
|
@@ -73,7 +84,10 @@ export interface NumberFieldProps extends Omit<BaseFieldProps, "value" | "onChan
|
|
|
73
84
|
/**
|
|
74
85
|
* Props for integer fields
|
|
75
86
|
*/
|
|
76
|
-
export interface IntegerFieldProps extends Omit<
|
|
87
|
+
export interface IntegerFieldProps extends Omit<
|
|
88
|
+
BaseFieldProps,
|
|
89
|
+
"value" | "onChange"
|
|
90
|
+
> {
|
|
77
91
|
fieldType: "integer";
|
|
78
92
|
value: number | null;
|
|
79
93
|
onChange: (value: number | null) => void;
|
|
@@ -85,7 +99,10 @@ export interface IntegerFieldProps extends Omit<BaseFieldProps, "value" | "onCha
|
|
|
85
99
|
/**
|
|
86
100
|
* Props for boolean fields
|
|
87
101
|
*/
|
|
88
|
-
export interface BooleanFieldProps extends Omit<
|
|
102
|
+
export interface BooleanFieldProps extends Omit<
|
|
103
|
+
BaseFieldProps,
|
|
104
|
+
"value" | "onChange"
|
|
105
|
+
> {
|
|
89
106
|
fieldType: "boolean";
|
|
90
107
|
value: boolean;
|
|
91
108
|
onChange: (value: boolean) => void;
|
|
@@ -94,7 +111,10 @@ export interface BooleanFieldProps extends Omit<BaseFieldProps, "value" | "onCha
|
|
|
94
111
|
/**
|
|
95
112
|
* Props for date fields
|
|
96
113
|
*/
|
|
97
|
-
export interface DateFieldProps extends Omit<
|
|
114
|
+
export interface DateFieldProps extends Omit<
|
|
115
|
+
BaseFieldProps,
|
|
116
|
+
"value" | "onChange"
|
|
117
|
+
> {
|
|
98
118
|
fieldType: "date";
|
|
99
119
|
value: string | null;
|
|
100
120
|
onChange: (value: string | null) => void;
|
|
@@ -103,7 +123,10 @@ export interface DateFieldProps extends Omit<BaseFieldProps, "value" | "onChange
|
|
|
103
123
|
/**
|
|
104
124
|
* Props for datetime fields
|
|
105
125
|
*/
|
|
106
|
-
export interface DateTimeFieldProps extends Omit<
|
|
126
|
+
export interface DateTimeFieldProps extends Omit<
|
|
127
|
+
BaseFieldProps,
|
|
128
|
+
"value" | "onChange"
|
|
129
|
+
> {
|
|
107
130
|
fieldType: "datetime";
|
|
108
131
|
value: string | null;
|
|
109
132
|
onChange: (value: string | null) => void;
|
|
@@ -112,7 +135,10 @@ export interface DateTimeFieldProps extends Omit<BaseFieldProps, "value" | "onCh
|
|
|
112
135
|
/**
|
|
113
136
|
* Props for select fields (single selection)
|
|
114
137
|
*/
|
|
115
|
-
export interface SelectFieldProps extends Omit<
|
|
138
|
+
export interface SelectFieldProps extends Omit<
|
|
139
|
+
BaseFieldProps,
|
|
140
|
+
"value" | "onChange"
|
|
141
|
+
> {
|
|
116
142
|
fieldType: "select";
|
|
117
143
|
value: string | null;
|
|
118
144
|
onChange: (value: string | null) => void;
|
|
@@ -122,7 +148,10 @@ export interface SelectFieldProps extends Omit<BaseFieldProps, "value" | "onChan
|
|
|
122
148
|
/**
|
|
123
149
|
* Props for multi-select fields
|
|
124
150
|
*/
|
|
125
|
-
export interface MultiSelectFieldProps extends Omit<
|
|
151
|
+
export interface MultiSelectFieldProps extends Omit<
|
|
152
|
+
BaseFieldProps,
|
|
153
|
+
"value" | "onChange"
|
|
154
|
+
> {
|
|
126
155
|
fieldType: "multiselect";
|
|
127
156
|
value: string[];
|
|
128
157
|
onChange: (value: string[]) => void;
|
|
@@ -189,7 +218,10 @@ export interface ArrayHelpers {
|
|
|
189
218
|
/** Swap items at two indices */
|
|
190
219
|
swap: (indexA: number, indexB: number) => void;
|
|
191
220
|
/** Get field props for an item field */
|
|
192
|
-
getItemFieldProps: (
|
|
221
|
+
getItemFieldProps: (
|
|
222
|
+
index: number,
|
|
223
|
+
fieldName: string,
|
|
224
|
+
) => ArrayItemFieldPropsResult;
|
|
193
225
|
/** Minimum number of items allowed */
|
|
194
226
|
minItems: number;
|
|
195
227
|
/** Maximum number of items allowed */
|
|
@@ -203,7 +235,10 @@ export interface ArrayHelpers {
|
|
|
203
235
|
/**
|
|
204
236
|
* Props for array fields
|
|
205
237
|
*/
|
|
206
|
-
export interface ArrayFieldProps extends Omit<
|
|
238
|
+
export interface ArrayFieldProps extends Omit<
|
|
239
|
+
BaseFieldProps,
|
|
240
|
+
"value" | "onChange"
|
|
241
|
+
> {
|
|
207
242
|
fieldType: "array";
|
|
208
243
|
value: unknown[];
|
|
209
244
|
onChange: (value: unknown[]) => void;
|
|
@@ -217,7 +252,10 @@ export interface ArrayFieldProps extends Omit<BaseFieldProps, "value" | "onChang
|
|
|
217
252
|
/**
|
|
218
253
|
* Props for object fields
|
|
219
254
|
*/
|
|
220
|
-
export interface ObjectFieldProps extends Omit<
|
|
255
|
+
export interface ObjectFieldProps extends Omit<
|
|
256
|
+
BaseFieldProps,
|
|
257
|
+
"value" | "onChange"
|
|
258
|
+
> {
|
|
221
259
|
fieldType: "object";
|
|
222
260
|
value: Record<string, unknown>;
|
|
223
261
|
onChange: (value: Record<string, unknown>) => void;
|
|
@@ -236,7 +274,10 @@ export interface ComputedFieldProps extends Omit<BaseFieldProps, "onChange"> {
|
|
|
236
274
|
/**
|
|
237
275
|
* Props for array item fields (within array context)
|
|
238
276
|
*/
|
|
239
|
-
export interface ArrayItemFieldProps extends Omit<
|
|
277
|
+
export interface ArrayItemFieldProps extends Omit<
|
|
278
|
+
BaseFieldProps,
|
|
279
|
+
"value" | "onChange"
|
|
280
|
+
> {
|
|
240
281
|
/** The field type */
|
|
241
282
|
fieldType: string;
|
|
242
283
|
/** Current value */
|
|
@@ -252,7 +293,10 @@ export interface ArrayItemFieldProps extends Omit<BaseFieldProps, "value" | "onC
|
|
|
252
293
|
/**
|
|
253
294
|
* Props for display fields (read-only presentation content)
|
|
254
295
|
*/
|
|
255
|
-
export interface DisplayFieldProps extends Omit<
|
|
296
|
+
export interface DisplayFieldProps extends Omit<
|
|
297
|
+
BaseFieldProps,
|
|
298
|
+
"value" | "onChange"
|
|
299
|
+
> {
|
|
256
300
|
fieldType: "display";
|
|
257
301
|
/** Static content (markdown/text) */
|
|
258
302
|
content?: string;
|
package/src/useForma.ts
CHANGED
|
@@ -237,6 +237,23 @@ function getDefaultBooleanValues(spec: Forma): Record<string, boolean> {
|
|
|
237
237
|
return defaults;
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Get default values from field definitions.
|
|
242
|
+
* Collects `defaultValue` from each field that specifies one.
|
|
243
|
+
* These are applied after boolean defaults but before initialData,
|
|
244
|
+
* so explicit defaults override type-implicit defaults,
|
|
245
|
+
* and runtime initialData overrides everything.
|
|
246
|
+
*/
|
|
247
|
+
function getFieldDefaults(spec: Forma): Record<string, unknown> {
|
|
248
|
+
const defaults: Record<string, unknown> = {};
|
|
249
|
+
for (const [fieldPath, fieldDef] of Object.entries(spec.fields)) {
|
|
250
|
+
if (fieldDef.defaultValue !== undefined) {
|
|
251
|
+
defaults[fieldPath] = fieldDef.defaultValue;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return defaults;
|
|
255
|
+
}
|
|
256
|
+
|
|
240
257
|
/**
|
|
241
258
|
* Main Forma hook
|
|
242
259
|
*/
|
|
@@ -264,7 +281,11 @@ export function useForma(options: UseFormaOptions): UseFormaReturn {
|
|
|
264
281
|
}, [inputSpec, referenceData]);
|
|
265
282
|
|
|
266
283
|
const [state, dispatch] = useReducer(formReducer, {
|
|
267
|
-
data: {
|
|
284
|
+
data: {
|
|
285
|
+
...getDefaultBooleanValues(spec),
|
|
286
|
+
...getFieldDefaults(spec),
|
|
287
|
+
...initialData,
|
|
288
|
+
},
|
|
268
289
|
touched: {},
|
|
269
290
|
isSubmitting: false,
|
|
270
291
|
isSubmitted: false,
|
|
@@ -448,8 +469,15 @@ export function useForma(options: UseFormaOptions): UseFormaReturn {
|
|
|
448
469
|
}, [immediateValidation, onSubmit, state.data]);
|
|
449
470
|
|
|
450
471
|
const resetForm = useCallback(() => {
|
|
451
|
-
dispatch({
|
|
452
|
-
|
|
472
|
+
dispatch({
|
|
473
|
+
type: "RESET",
|
|
474
|
+
initialData: {
|
|
475
|
+
...getDefaultBooleanValues(spec),
|
|
476
|
+
...getFieldDefaults(spec),
|
|
477
|
+
...initialData,
|
|
478
|
+
},
|
|
479
|
+
});
|
|
480
|
+
}, [spec, initialData]);
|
|
453
481
|
|
|
454
482
|
// Wizard helpers
|
|
455
483
|
const wizard = useMemo((): WizardHelpers | null => {
|