@fogpipe/forma-react 0.9.0 → 0.10.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 +11 -1
- package/dist/index.js +65 -89
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/FormRenderer.tsx +38 -84
- package/src/__tests__/FormRenderer.test.tsx +167 -0
- package/src/__tests__/canProceed.test.ts +11 -8
- package/src/__tests__/diabetes-trial-flow.test.ts +41 -24
- package/src/__tests__/null-handling.test.ts +53 -50
- package/src/__tests__/useForma.test.ts +129 -0
- package/src/types.ts +11 -1
- package/src/useForma.ts +28 -1
package/src/types.ts
CHANGED
|
@@ -300,6 +300,11 @@ export interface FieldWrapperProps {
|
|
|
300
300
|
errors: FieldError[];
|
|
301
301
|
touched: boolean;
|
|
302
302
|
required: boolean;
|
|
303
|
+
/**
|
|
304
|
+
* Whether to show the required indicator in the UI.
|
|
305
|
+
* False for boolean fields since false is a valid answer.
|
|
306
|
+
*/
|
|
307
|
+
showRequiredIndicator: boolean;
|
|
303
308
|
visible: boolean;
|
|
304
309
|
}
|
|
305
310
|
|
|
@@ -422,8 +427,13 @@ export interface GetFieldPropsResult {
|
|
|
422
427
|
visible: boolean;
|
|
423
428
|
/** Whether field is enabled (not disabled) */
|
|
424
429
|
enabled: boolean;
|
|
425
|
-
/** Whether field is required */
|
|
430
|
+
/** Whether field is required (for validation) */
|
|
426
431
|
required: boolean;
|
|
432
|
+
/**
|
|
433
|
+
* Whether to show the required indicator in the UI.
|
|
434
|
+
* False for boolean fields since false is a valid answer.
|
|
435
|
+
*/
|
|
436
|
+
showRequiredIndicator: boolean;
|
|
427
437
|
/** Whether field has been touched */
|
|
428
438
|
touched: boolean;
|
|
429
439
|
/** Validation errors for this field */
|
package/src/useForma.ts
CHANGED
|
@@ -193,6 +193,23 @@ function formReducer(state: FormState, action: FormAction): FormState {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Get default initial values for boolean fields.
|
|
198
|
+
* Boolean fields default to false to avoid undefined state,
|
|
199
|
+
* which provides better UX since false is a valid answer.
|
|
200
|
+
*/
|
|
201
|
+
function getDefaultBooleanValues(spec: Forma): Record<string, boolean> {
|
|
202
|
+
const defaults: Record<string, boolean> = {};
|
|
203
|
+
for (const fieldPath of spec.fieldOrder) {
|
|
204
|
+
const schemaProperty = spec.schema.properties?.[fieldPath];
|
|
205
|
+
const fieldDef = spec.fields[fieldPath];
|
|
206
|
+
if (schemaProperty?.type === "boolean" || fieldDef?.type === "boolean") {
|
|
207
|
+
defaults[fieldPath] = false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return defaults;
|
|
211
|
+
}
|
|
212
|
+
|
|
196
213
|
/**
|
|
197
214
|
* Main Forma hook
|
|
198
215
|
*/
|
|
@@ -212,7 +229,7 @@ export function useForma(options: UseFormaOptions): UseFormaReturn {
|
|
|
212
229
|
}, [inputSpec, referenceData]);
|
|
213
230
|
|
|
214
231
|
const [state, dispatch] = useReducer(formReducer, {
|
|
215
|
-
data: initialData,
|
|
232
|
+
data: { ...getDefaultBooleanValues(spec), ...initialData }, // Boolean defaults merged UNDER initialData
|
|
216
233
|
touched: {},
|
|
217
234
|
isSubmitting: false,
|
|
218
235
|
isSubmitted: false,
|
|
@@ -569,6 +586,14 @@ export function useForma(options: UseFormaOptions): UseFormaReturn {
|
|
|
569
586
|
const hasErrors = displayedErrors.length > 0;
|
|
570
587
|
const isRequired = required[path] ?? false;
|
|
571
588
|
|
|
589
|
+
// Boolean fields: hide asterisk unless they have validation rules (consent pattern)
|
|
590
|
+
// - Binary question ("Do you smoke?"): no validation → false is valid → hide asterisk
|
|
591
|
+
// - Consent checkbox ("I accept terms"): has validation rule → show asterisk
|
|
592
|
+
const schemaProperty = spec.schema.properties[path];
|
|
593
|
+
const isBooleanField = schemaProperty?.type === "boolean" || fieldDef?.type === "boolean";
|
|
594
|
+
const hasValidationRules = (fieldDef?.validations?.length ?? 0) > 0;
|
|
595
|
+
const showRequiredIndicator = isRequired && (!isBooleanField || hasValidationRules);
|
|
596
|
+
|
|
572
597
|
return {
|
|
573
598
|
name: path,
|
|
574
599
|
value: getValueAtPath(path),
|
|
@@ -579,6 +604,7 @@ export function useForma(options: UseFormaOptions): UseFormaReturn {
|
|
|
579
604
|
visible: visibility[path] !== false,
|
|
580
605
|
enabled: enabled[path] !== false,
|
|
581
606
|
required: isRequired,
|
|
607
|
+
showRequiredIndicator,
|
|
582
608
|
touched: isTouched,
|
|
583
609
|
errors: displayedErrors,
|
|
584
610
|
onChange: handlers.onChange,
|
|
@@ -634,6 +660,7 @@ export function useForma(options: UseFormaOptions): UseFormaReturn {
|
|
|
634
660
|
visible: true,
|
|
635
661
|
enabled: enabled[path] !== false,
|
|
636
662
|
required: false, // TODO: Evaluate item field required
|
|
663
|
+
showRequiredIndicator: false, // Item fields don't show required indicator
|
|
637
664
|
touched: isTouched,
|
|
638
665
|
errors: showErrors ? fieldErrors : [],
|
|
639
666
|
onChange: handlers.onChange,
|