@fogpipe/forma-react 0.8.2 → 0.10.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/dist/index.d.ts +11 -1
- package/dist/index.js +45 -19
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/FormRenderer.tsx +11 -3
- package/src/__tests__/canProceed.test.ts +11 -8
- package/src/__tests__/diabetes-trial-flow.test.ts +793 -0
- package/src/__tests__/null-handling.test.ts +439 -0
- package/src/__tests__/useForma.test.ts +129 -0
- package/src/types.ts +11 -1
- package/src/useForma.ts +28 -1
package/src/FormRenderer.tsx
CHANGED
|
@@ -74,7 +74,7 @@ function DefaultLayout({ children, onSubmit, isSubmitting }: LayoutProps) {
|
|
|
74
74
|
/**
|
|
75
75
|
* Default field wrapper component with accessibility support
|
|
76
76
|
*/
|
|
77
|
-
function DefaultFieldWrapper({ fieldPath, field, children, errors,
|
|
77
|
+
function DefaultFieldWrapper({ fieldPath, field, children, errors, showRequiredIndicator, visible }: FieldWrapperProps) {
|
|
78
78
|
if (!visible) return null;
|
|
79
79
|
|
|
80
80
|
const errorId = `${fieldPath}-error`;
|
|
@@ -86,8 +86,8 @@ function DefaultFieldWrapper({ fieldPath, field, children, errors, required, vis
|
|
|
86
86
|
{field.label && (
|
|
87
87
|
<label htmlFor={fieldPath}>
|
|
88
88
|
{field.label}
|
|
89
|
-
{
|
|
90
|
-
{
|
|
89
|
+
{showRequiredIndicator && <span className="required" aria-hidden="true">*</span>}
|
|
90
|
+
{showRequiredIndicator && <span className="sr-only"> (required)</span>}
|
|
91
91
|
</label>
|
|
92
92
|
)}
|
|
93
93
|
{children}
|
|
@@ -274,6 +274,13 @@ export const FormRenderer = forwardRef<FormRendererHandle, FormRendererProps>(
|
|
|
274
274
|
// Get schema property for additional constraints
|
|
275
275
|
const schemaProperty = spec.schema.properties[fieldPath];
|
|
276
276
|
|
|
277
|
+
// Boolean fields: hide asterisk unless they have validation rules (consent pattern)
|
|
278
|
+
// - Binary question ("Do you smoke?"): no validation → false is valid → hide asterisk
|
|
279
|
+
// - Consent checkbox ("I accept terms"): has validation rule → show asterisk
|
|
280
|
+
const isBooleanField = schemaProperty?.type === "boolean" || fieldDef?.type === "boolean";
|
|
281
|
+
const hasValidationRules = (fieldDef?.validations?.length ?? 0) > 0;
|
|
282
|
+
const showRequiredIndicator = required && (!isBooleanField || hasValidationRules);
|
|
283
|
+
|
|
277
284
|
// Base field props
|
|
278
285
|
const baseProps: BaseFieldProps = {
|
|
279
286
|
name: fieldPath,
|
|
@@ -429,6 +436,7 @@ export const FormRenderer = forwardRef<FormRendererHandle, FormRendererProps>(
|
|
|
429
436
|
errors={errors}
|
|
430
437
|
touched={touched}
|
|
431
438
|
required={required}
|
|
439
|
+
showRequiredIndicator={showRequiredIndicator}
|
|
432
440
|
visible={isVisible}
|
|
433
441
|
>
|
|
434
442
|
{React.createElement(Component as React.ComponentType<typeof componentProps>, componentProps)}
|
|
@@ -249,10 +249,12 @@ describe("canProceed", () => {
|
|
|
249
249
|
expect(result.current.wizard?.canProceed).toBe(true);
|
|
250
250
|
});
|
|
251
251
|
|
|
252
|
-
it("required boolean fields -
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
//
|
|
252
|
+
it("required boolean fields - auto-initialized to false", () => {
|
|
253
|
+
// Boolean fields are auto-initialized to false, which is a valid value.
|
|
254
|
+
// This provides better UX - the form is valid from the start since
|
|
255
|
+
// false is a valid answer to "Do you have pets?"
|
|
256
|
+
//
|
|
257
|
+
// For checkboxes that must be checked (like "Accept Terms"),
|
|
256
258
|
// use a validation rule: { rule: "value = true", message: "Must accept terms" }
|
|
257
259
|
const spec = createTestSpec({
|
|
258
260
|
fields: {
|
|
@@ -263,13 +265,14 @@ describe("canProceed", () => {
|
|
|
263
265
|
],
|
|
264
266
|
});
|
|
265
267
|
|
|
266
|
-
//
|
|
267
|
-
const { result:
|
|
268
|
+
// With auto-initialization, boolean defaults to false (a valid value)
|
|
269
|
+
const { result: resultDefault } = renderHook(() =>
|
|
268
270
|
useForma({ spec, initialData: {} })
|
|
269
271
|
);
|
|
270
|
-
expect(
|
|
272
|
+
expect(resultDefault.current.data.hasPets).toBe(false);
|
|
273
|
+
expect(resultDefault.current.wizard?.canProceed).toBe(true); // false is valid
|
|
271
274
|
|
|
272
|
-
// false should be valid (user answered "no")
|
|
275
|
+
// explicit false should be valid (user answered "no")
|
|
273
276
|
const { result: resultFalse } = renderHook(() =>
|
|
274
277
|
useForma({ spec, initialData: { hasPets: false } })
|
|
275
278
|
);
|