@digigov/form 2.0.0-555d1027 → 2.0.0-5e7a6790
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/Field/ErrorGroup.d.ts +3 -3
- package/Field/ErrorGroup.js.map +1 -1
- package/Field/FieldBase/index.js +0 -1
- package/Field/FieldBase.d.ts +1 -1
- package/Field/FieldBase.js.map +2 -2
- package/Field/FieldBaseContainer/index.js +36 -34
- package/Field/FieldBaseContainer.d.ts +2 -2
- package/Field/FieldBaseContainer.js.map +2 -2
- package/Field/FieldConditional.d.ts +1 -1
- package/Field/FieldConditional.js.map +1 -1
- package/Field/index.d.ts +1 -1
- package/Field/index.js +15 -25
- package/Field/index.js.map +2 -2
- package/Field/types.d.ts +11 -10
- package/Field/utils/evaluateFieldWithConditions.d.ts +2 -2
- package/Field/utils/evaluateFieldWithConditions.js.map +2 -2
- package/Field/utils/resolveField/index.js +35 -0
- package/Field/utils/{calculateField → resolveField}/package.json +1 -1
- package/Field/utils/resolveField.d.ts +3 -0
- package/Field/utils/resolveField.js.map +7 -0
- package/Field/utils/useField/index.js +4 -2
- package/Field/utils/useField.d.ts +1 -1
- package/Field/utils/useField.js.map +2 -2
- package/FieldArray/BaseFieldArray/index.js +76 -0
- package/{inputs/inputsScenarios → FieldArray/BaseFieldArray}/package.json +1 -1
- package/FieldArray/BaseFieldArray.d.ts +5 -0
- package/FieldArray/BaseFieldArray.js.map +7 -0
- package/FieldArray/FormDialog/ArrayDisplay/ArrayDisplay.stories.d.ts +10 -0
- package/FieldArray/FormDialog/ArrayDisplay/ArrayItemDisplay/index.js +97 -0
- package/FieldArray/FormDialog/ArrayDisplay/ArrayItemDisplay/package.json +6 -0
- package/FieldArray/FormDialog/ArrayDisplay/ArrayItemDisplay.d.ts +22 -0
- package/FieldArray/FormDialog/ArrayDisplay/ArrayItemDisplay.js.map +7 -0
- package/FieldArray/FormDialog/ArrayDisplay/ArrayItemHeader/index.js +11 -0
- package/FieldArray/FormDialog/ArrayDisplay/ArrayItemHeader/package.json +6 -0
- package/FieldArray/FormDialog/ArrayDisplay/ArrayItemHeader.d.ts +5 -0
- package/FieldArray/FormDialog/ArrayDisplay/ArrayItemHeader.js.map +7 -0
- package/FieldArray/FormDialog/ArrayDisplay/__stories__/Default.d.ts +2 -0
- package/FieldArray/FormDialog/ArrayDisplay/__stories__/ReadOnly.d.ts +2 -0
- package/FieldArray/FormDialog/ArrayDisplay/__stories__/Sortable.d.ts +2 -0
- package/FieldArray/FormDialog/ArrayDisplay/index.d.ts +14 -0
- package/FieldArray/FormDialog/ArrayDisplay/index.js +51 -0
- package/FieldArray/FormDialog/ArrayDisplay/index.js.map +7 -0
- package/FieldArray/FormDialog/ArrayDisplay/package.json +6 -0
- package/FieldArray/FormDialog/ArrayEditModal/index.js +149 -0
- package/FieldArray/FormDialog/ArrayEditModal/package.json +6 -0
- package/FieldArray/FormDialog/ArrayEditModal.d.ts +26 -0
- package/FieldArray/FormDialog/ArrayEditModal.js.map +7 -0
- package/FieldArray/FormDialog/index.d.ts +19 -0
- package/FieldArray/FormDialog/index.js +139 -348
- package/FieldArray/FormDialog/index.js.map +7 -0
- package/FieldArray/index.d.ts +4 -2
- package/FieldArray/index.js +28 -54
- package/FieldArray/index.js.map +2 -2
- package/FieldObject/index.d.ts +4 -4
- package/FieldObject/index.js +5 -12
- package/FieldObject/index.js.map +2 -2
- package/Fieldset/FieldsetWithContext.js.map +1 -1
- package/Fieldset/index.d.ts +1 -1
- package/Fieldset/index.js.map +1 -1
- package/Fieldset/types.d.ts +2 -2
- package/FormBuilder/index.d.ts +1 -1
- package/FormBuilder/index.js +155 -130
- package/FormBuilder/index.js.map +2 -2
- package/FormContext.d.ts +2 -2
- package/FormContext.js.map +2 -2
- package/MultiplicityField/add-objects/index.js +10 -19
- package/MultiplicityField/add-objects.d.ts +1 -1
- package/MultiplicityField/add-objects.js.map +2 -2
- package/MultiplicityField/index.d.ts +2 -2
- package/MultiplicityField/index.js.map +2 -2
- package/MultiplicityField/types.d.ts +2 -2
- package/Questions/Questions.d.ts +1 -1
- package/Questions/Questions.js.map +1 -1
- package/Questions/QuestionsContext.d.ts +1 -1
- package/Questions/QuestionsContext.js.map +1 -1
- package/Questions/Step/Step.d.ts +1 -1
- package/Questions/Step/Step.js.map +1 -1
- package/Questions/Step/StepArrayReview.d.ts +1 -1
- package/Questions/Step/StepArrayReview.js.map +1 -1
- package/Questions/Step/StepContext.d.ts +1 -1
- package/Questions/Step/StepContext.js.map +1 -1
- package/Questions/Step/StepDescription.d.ts +1 -1
- package/Questions/Step/StepDescription.js.map +2 -2
- package/Questions/Step/StepForm.d.ts +2 -2
- package/Questions/Step/StepForm.js.map +1 -1
- package/Questions/Step/StepQuote.d.ts +1 -1
- package/Questions/Step/StepQuote.js.map +2 -2
- package/Questions/Step/StepTitle.d.ts +1 -1
- package/Questions/Step/StepTitle.js.map +2 -2
- package/Questions/Step/getAddMoreFields.d.ts +2 -2
- package/Questions/Step/getAddMoreFields.js.map +1 -1
- package/Questions/Step/types.d.ts +1 -1
- package/Questions/getNextStep.d.ts +1 -1
- package/Questions/getNextStep.js.map +1 -1
- package/Questions/types.d.ts +1 -1
- package/cjs/Field/ErrorGroup.js.map +1 -1
- package/cjs/Field/FieldBase/index.js +0 -1
- package/cjs/Field/FieldBase.js.map +2 -2
- package/cjs/Field/FieldBaseContainer/index.js +36 -34
- package/cjs/Field/FieldBaseContainer.js.map +2 -2
- package/cjs/Field/FieldConditional.js.map +1 -1
- package/cjs/Field/index.js +15 -22
- package/cjs/Field/index.js.map +2 -2
- package/cjs/Field/types.js.map +1 -1
- package/cjs/Field/utils/evaluateFieldWithConditions.js.map +2 -2
- package/cjs/Field/utils/resolveField/index.js +55 -0
- package/cjs/Field/utils/resolveField.js.map +7 -0
- package/cjs/Field/utils/useField/index.js +4 -2
- package/cjs/Field/utils/useField.js.map +2 -2
- package/cjs/FieldArray/BaseFieldArray/index.js +109 -0
- package/cjs/FieldArray/BaseFieldArray.js.map +7 -0
- package/cjs/FieldArray/FormDialog/ArrayDisplay/ArrayItemDisplay/index.js +130 -0
- package/cjs/FieldArray/FormDialog/ArrayDisplay/ArrayItemDisplay.js.map +7 -0
- package/cjs/{locales/el → FieldArray/FormDialog/ArrayDisplay/ArrayItemHeader}/index.js +16 -7
- package/cjs/FieldArray/FormDialog/ArrayDisplay/ArrayItemHeader.js.map +7 -0
- package/cjs/FieldArray/FormDialog/ArrayDisplay/index.js +84 -0
- package/cjs/FieldArray/FormDialog/ArrayDisplay/index.js.map +7 -0
- package/cjs/FieldArray/FormDialog/ArrayEditModal/index.js +177 -0
- package/cjs/FieldArray/FormDialog/ArrayEditModal.js.map +7 -0
- package/cjs/FieldArray/FormDialog/index.js +130 -331
- package/cjs/FieldArray/FormDialog/index.js.map +7 -0
- package/cjs/FieldArray/index.js +27 -53
- package/cjs/FieldArray/index.js.map +3 -3
- package/cjs/FieldObject/index.js +5 -9
- package/cjs/FieldObject/index.js.map +2 -2
- package/cjs/Fieldset/FieldsetWithContext.js.map +1 -1
- package/cjs/Fieldset/index.js.map +1 -1
- package/cjs/Fieldset/types.js.map +1 -1
- package/cjs/FormBuilder/index.js +156 -131
- package/cjs/FormBuilder/index.js.map +3 -3
- package/cjs/FormContext.js.map +2 -2
- package/cjs/MultiplicityField/add-objects/index.js +9 -13
- package/cjs/MultiplicityField/add-objects.js.map +2 -2
- package/cjs/MultiplicityField/index.js.map +2 -2
- package/cjs/MultiplicityField/types.js.map +1 -1
- package/cjs/Questions/Questions.js.map +1 -1
- package/cjs/Questions/QuestionsContext.js.map +1 -1
- package/cjs/Questions/Step/Step.js.map +1 -1
- package/cjs/Questions/Step/StepArrayReview.js.map +1 -1
- package/cjs/Questions/Step/StepContext.js.map +1 -1
- package/cjs/Questions/Step/StepDescription.js.map +2 -2
- package/cjs/Questions/Step/StepForm.js.map +1 -1
- package/cjs/Questions/Step/StepQuote.js.map +2 -2
- package/cjs/Questions/Step/StepTitle.js.map +2 -2
- package/cjs/Questions/Step/getAddMoreFields.js.map +1 -1
- package/cjs/Questions/Step/types.js.map +1 -1
- package/cjs/Questions/getNextStep.js.map +1 -1
- package/cjs/Questions/types.js.map +1 -1
- package/cjs/hooks/useFieldFocusManager/index.js +135 -0
- package/cjs/hooks/useFieldFocusManager.js.map +7 -0
- package/cjs/hooks/utils/index.js +98 -0
- package/cjs/hooks/utils.js.map +7 -0
- package/cjs/inputs/AutoCompleteInput/index.js.map +2 -2
- package/cjs/inputs/Checkboxes/index.js +67 -63
- package/cjs/inputs/Checkboxes/index.js.map +2 -2
- package/cjs/inputs/DateInput/index.js +10 -5
- package/cjs/inputs/DateInput/index.js.map +2 -2
- package/cjs/inputs/DateTimeInput/index.js +10 -5
- package/cjs/inputs/DateTimeInput/index.js.map +2 -2
- package/cjs/inputs/FileInput/index.js.map +2 -2
- package/cjs/inputs/ImageInput/index.js.map +1 -1
- package/cjs/inputs/Input/index.js +54 -56
- package/cjs/inputs/Input/index.js.map +2 -2
- package/cjs/inputs/Label/index.js.map +1 -1
- package/cjs/inputs/OtpInput/index.js +36 -31
- package/cjs/inputs/OtpInput/index.js.map +2 -2
- package/cjs/inputs/Radio/index.js +5 -4
- package/cjs/inputs/Radio/index.js.map +3 -3
- package/cjs/inputs/Select/index.js.map +1 -1
- package/cjs/{Field/utils → inputs/registry}/index.js +4 -4
- package/cjs/inputs/registry.js.map +7 -0
- package/cjs/{lazy/index.js → lazy.js} +10 -9
- package/cjs/lazy.js.map +2 -2
- package/cjs/{registry/index.js → registry.js} +23 -13
- package/cjs/registry.js.map +2 -2
- package/cjs/types.js.map +1 -1
- package/cjs/utils.js.map +2 -2
- package/cjs/validators/index.js.map +2 -2
- package/cjs/validators/types.js.map +1 -1
- package/cjs/validators/utils/date/index.js +6 -1
- package/cjs/validators/utils/date.js.map +2 -2
- package/cjs/validators/utils/datetime/index.js +6 -1
- package/cjs/validators/utils/datetime.js.map +2 -2
- package/cjs/validators/utils/file.js.map +1 -1
- package/cjs/validators/utils/iban.js.map +1 -1
- package/cjs/validators/utils/image.js.map +1 -1
- package/cjs/validators/utils/index.js.map +1 -1
- package/cjs/validators/utils/int.js.map +1 -1
- package/cjs/validators/utils/number.js.map +1 -1
- package/cjs/validators/utils/otp.js.map +1 -1
- package/cjs/validators/utils/phone.js.map +1 -1
- package/cjs/validators/utils/postal_code.js.map +1 -1
- package/cjs/validators/utils/text_limit.js.map +1 -1
- package/hooks/useFieldFocusManager/index.js +116 -0
- package/hooks/useFieldFocusManager/package.json +6 -0
- package/hooks/useFieldFocusManager.d.ts +25 -0
- package/hooks/useFieldFocusManager.js.map +7 -0
- package/hooks/utils/index.js +73 -0
- package/{Field → hooks}/utils/package.json +1 -1
- package/hooks/utils.d.ts +18 -0
- package/hooks/utils.js.map +7 -0
- package/index.js +1 -1
- package/inputs/AutoCompleteInput/index.d.ts +3 -3
- package/inputs/AutoCompleteInput/index.js +1 -3
- package/inputs/AutoCompleteInput/index.js.map +2 -2
- package/inputs/Checkboxes/index.d.ts +3 -3
- package/inputs/Checkboxes/index.js +67 -63
- package/inputs/Checkboxes/index.js.map +2 -2
- package/inputs/DateInput/index.d.ts +2 -5
- package/inputs/DateInput/index.js +10 -5
- package/inputs/DateInput/index.js.map +2 -2
- package/inputs/DateTimeInput/index.d.ts +2 -5
- package/inputs/DateTimeInput/index.js +10 -5
- package/inputs/DateTimeInput/index.js.map +2 -2
- package/inputs/FileInput/index.d.ts +4 -4
- package/inputs/FileInput/index.js +1 -4
- package/inputs/FileInput/index.js.map +2 -2
- package/inputs/ImageInput/index.d.ts +2 -2
- package/inputs/ImageInput/index.js.map +1 -1
- package/inputs/Input/index.d.ts +2 -2
- package/inputs/Input/index.js +54 -56
- package/inputs/Input/index.js.map +2 -2
- package/inputs/Label/index.d.ts +1 -1
- package/inputs/Label/index.js.map +1 -1
- package/inputs/OtpInput/index.d.ts +1 -5
- package/inputs/OtpInput/index.js +36 -31
- package/inputs/OtpInput/index.js.map +2 -2
- package/inputs/Radio/index.d.ts +4 -4
- package/inputs/Radio/index.js +5 -4
- package/inputs/Radio/index.js.map +3 -3
- package/inputs/Select/index.d.ts +2 -2
- package/inputs/Select/index.js.map +1 -1
- package/{Field/utils → inputs/registry}/index.js +1 -1
- package/{locales/el → inputs/registry}/package.json +1 -1
- package/{Field/utils/index.d.ts → inputs/registry.d.ts} +2 -1
- package/inputs/registry.js.map +7 -0
- package/lazy/index.js +10 -9
- package/package.json +4 -4
- package/registry/index.js +23 -13
- package/src/Field/ErrorGroup.tsx +3 -3
- package/src/Field/FieldBase.tsx +1 -2
- package/src/Field/FieldBaseContainer.tsx +68 -58
- package/src/Field/FieldConditional.tsx +1 -1
- package/src/Field/index.tsx +15 -33
- package/src/Field/types.tsx +11 -12
- package/src/Field/utils/evaluateFieldWithConditions.ts +5 -2
- package/src/Field/utils/resolveField.ts +58 -0
- package/src/Field/utils/useField.ts +3 -1
- package/src/FieldArray/BaseFieldArray.tsx +97 -0
- package/src/FieldArray/FormDialog/ArrayDisplay/ArrayDisplay.stories.js +11 -0
- package/src/FieldArray/FormDialog/ArrayDisplay/ArrayItemDisplay.tsx +165 -0
- package/src/FieldArray/FormDialog/ArrayDisplay/ArrayItemHeader.tsx +15 -0
- package/src/FieldArray/FormDialog/ArrayDisplay/__stories__/Default.tsx +93 -0
- package/src/FieldArray/FormDialog/ArrayDisplay/__stories__/ReadOnly.tsx +79 -0
- package/src/FieldArray/FormDialog/ArrayDisplay/__stories__/Sortable.tsx +93 -0
- package/src/FieldArray/FormDialog/ArrayDisplay/index.tsx +75 -0
- package/src/FieldArray/FormDialog/ArrayEditModal.tsx +233 -0
- package/src/FieldArray/FormDialog/index.tsx +247 -0
- package/src/FieldArray/__tests__/fieldset-multiplicity.spec.tsx +271 -0
- package/src/FieldArray/__tests__/multiplicity-attachment.spec.tsx +280 -0
- package/src/FieldArray/__tests__/multiplicity-required.spec.tsx +131 -0
- package/src/FieldArray/__tests__/nested-fieldset-multiplicity.spec.tsx +627 -0
- package/src/FieldArray/__tests__/preference-multiple-choice.spec.tsx +222 -0
- package/src/FieldArray/index.spec.tsx +355 -0
- package/src/FieldArray/index.tsx +34 -61
- package/src/FieldObject/index.tsx +9 -17
- package/src/Fieldset/FieldsetWithContext.tsx +1 -1
- package/src/Fieldset/index.tsx +1 -1
- package/src/Fieldset/types.tsx +2 -2
- package/src/FormBuilder/index.tsx +188 -142
- package/src/FormBuilder/scenarios.test.tsx +759 -1
- package/src/FormContext.tsx +3 -2
- package/src/MultiplicityField/add-objects.tsx +12 -21
- package/src/MultiplicityField/index.tsx +3 -2
- package/src/MultiplicityField/types.ts +5 -2
- package/src/Questions/Questions.tsx +2 -2
- package/src/Questions/QuestionsContext.tsx +1 -1
- package/src/Questions/Step/Step.tsx +1 -1
- package/src/Questions/Step/StepArrayReview.tsx +2 -2
- package/src/Questions/Step/StepContext.tsx +1 -1
- package/src/Questions/Step/StepDescription.tsx +2 -1
- package/src/Questions/Step/StepForm.tsx +2 -2
- package/src/Questions/Step/StepQuote.tsx +2 -1
- package/src/Questions/Step/StepTitle.tsx +2 -1
- package/src/Questions/Step/getAddMoreFields.tsx +2 -2
- package/src/Questions/Step/types.tsx +1 -1
- package/src/Questions/getNextStep.tsx +1 -1
- package/src/Questions/types.tsx +1 -1
- package/src/hooks/__tests__/useFieldFocusManager.spec.tsx +1079 -0
- package/src/hooks/__tests__/utils.spec.ts +568 -0
- package/src/hooks/useFieldFocusManager.ts +162 -0
- package/src/hooks/utils.ts +122 -0
- package/src/inputs/AutoCompleteInput/index.tsx +4 -6
- package/src/inputs/Checkboxes/index.tsx +95 -87
- package/src/inputs/DateInput/index.tsx +19 -6
- package/src/inputs/DateTimeInput/index.tsx +19 -6
- package/src/inputs/FileInput/index.tsx +9 -7
- package/src/inputs/ImageInput/index.tsx +2 -2
- package/src/inputs/Input/index.tsx +72 -71
- package/src/inputs/Label/index.tsx +1 -1
- package/src/inputs/OtpInput/index.tsx +43 -34
- package/src/inputs/Radio/index.tsx +29 -21
- package/src/inputs/Select/index.tsx +2 -2
- package/src/{Field/utils/index.ts → inputs/registry.ts} +3 -1
- package/src/lazy.js +10 -9
- package/src/registry.js +23 -13
- package/src/types.tsx +12 -5
- package/src/utils.ts +3 -2
- package/src/validators/index.ts +10 -9
- package/src/validators/types.ts +1 -1
- package/src/validators/utils/date.ts +8 -3
- package/src/validators/utils/datetime.ts +8 -3
- package/src/validators/utils/file.ts +2 -2
- package/src/validators/utils/iban.ts +2 -2
- package/src/validators/utils/image.ts +2 -2
- package/src/validators/utils/index.ts +2 -2
- package/src/validators/utils/int.ts +1 -1
- package/src/validators/utils/number.ts +1 -1
- package/src/validators/utils/otp.ts +2 -2
- package/src/validators/utils/phone.ts +2 -2
- package/src/validators/utils/postal_code.ts +2 -2
- package/src/validators/utils/text_limit.ts +2 -2
- package/types.d.ts +8 -4
- package/types.js.map +1 -1
- package/utils.d.ts +1 -1
- package/utils.js.map +2 -2
- package/validators/index.d.ts +5 -5
- package/validators/index.js.map +2 -2
- package/validators/types.d.ts +1 -1
- package/validators/utils/date/index.js +6 -1
- package/validators/utils/date.d.ts +2 -2
- package/validators/utils/date.js.map +2 -2
- package/validators/utils/datetime/index.js +6 -1
- package/validators/utils/datetime.d.ts +2 -2
- package/validators/utils/datetime.js.map +2 -2
- package/validators/utils/file.d.ts +2 -2
- package/validators/utils/file.js.map +1 -1
- package/validators/utils/iban.d.ts +2 -2
- package/validators/utils/iban.js.map +1 -1
- package/validators/utils/image.d.ts +2 -2
- package/validators/utils/image.js.map +1 -1
- package/validators/utils/index.d.ts +2 -2
- package/validators/utils/index.js.map +1 -1
- package/validators/utils/int.d.ts +1 -1
- package/validators/utils/int.js.map +1 -1
- package/validators/utils/number.d.ts +1 -1
- package/validators/utils/number.js.map +1 -1
- package/validators/utils/otp.d.ts +2 -2
- package/validators/utils/otp.js.map +1 -1
- package/validators/utils/phone.d.ts +2 -2
- package/validators/utils/phone.js.map +1 -1
- package/validators/utils/postal_code.d.ts +2 -2
- package/validators/utils/postal_code.js.map +1 -1
- package/validators/utils/text_limit.d.ts +2 -2
- package/validators/utils/text_limit.js.map +1 -1
- package/Field/utils/calculateField/index.js +0 -27
- package/Field/utils/calculateField.d.ts +0 -2
- package/Field/utils/calculateField.js.map +0 -7
- package/Field/utils/index.js.map +0 -7
- package/FieldArray/FormDialog.d.ts +0 -67
- package/FieldArray/FormDialog.js.map +0 -7
- package/FormBuilder/index.test.d.ts +0 -1
- package/FormBuilder/interaction.test.d.ts +0 -1
- package/FormBuilder/scenarios.test.d.ts +0 -88
- package/MultiplicityField/index.test.d.ts +0 -1
- package/Questions/index.spec.d.ts +0 -1
- package/Questions/index.test.d.ts +0 -1
- package/cjs/Field/utils/calculateField/index.js +0 -50
- package/cjs/Field/utils/calculateField.js.map +0 -7
- package/cjs/Field/utils/index.js.map +0 -7
- package/cjs/FieldArray/FormDialog.js.map +0 -7
- package/cjs/inputs/inputsScenarios/index.js +0 -533
- package/cjs/inputs/inputsScenarios.js.map +0 -7
- package/cjs/locales/el.js.map +0 -7
- package/inputs/AutoCompleteInput/index.test.d.ts +0 -1
- package/inputs/Checkboxes/index.test.d.ts +0 -1
- package/inputs/DateInput/index.test.d.ts +0 -1
- package/inputs/DateTimeInput/index.test.d.ts +0 -1
- package/inputs/FileInput/index.test.d.ts +0 -1
- package/inputs/ImageInput/index.test.d.ts +0 -1
- package/inputs/Input/index.test.d.ts +0 -1
- package/inputs/Label/index.test.d.ts +0 -1
- package/inputs/OtpInput/index.test.d.ts +0 -1
- package/inputs/Radio/index.test.d.ts +0 -1
- package/inputs/Select/index.test.d.ts +0 -1
- package/inputs/inputsScenarios/index.js +0 -499
- package/inputs/inputsScenarios.d.ts +0 -296
- package/inputs/inputsScenarios.js.map +0 -7
- package/locales/el/index.js +0 -6
- package/locales/el.d.ts +0 -2
- package/locales/el.js.map +0 -7
- package/src/Field/utils/calculateField.ts +0 -49
- package/src/FieldArray/FormDialog.tsx +0 -568
- package/src/inputs/inputsScenarios.ts +0 -496
- package/src/locales/el.ts +0 -3
- /package/{FieldArray/index.test.d.ts → hooks/__tests__/utils.spec.d.ts} +0 -0
|
@@ -0,0 +1,1079 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
renderHook,
|
|
4
|
+
act,
|
|
5
|
+
render,
|
|
6
|
+
screen,
|
|
7
|
+
fireEvent,
|
|
8
|
+
waitFor,
|
|
9
|
+
} from '@testing-library/react';
|
|
10
|
+
import { useForm, useFieldArray } from 'react-hook-form';
|
|
11
|
+
import { expect } from 'vitest';
|
|
12
|
+
|
|
13
|
+
import { useFieldFocusManager } from '@digigov/form/hooks/useFieldFocusManager';
|
|
14
|
+
|
|
15
|
+
// Mock component that uses the hook
|
|
16
|
+
function TestFormComponent({
|
|
17
|
+
enabled = true,
|
|
18
|
+
onRegisterElement,
|
|
19
|
+
}: {
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
onRegisterElement?: (register: any) => void;
|
|
22
|
+
}) {
|
|
23
|
+
const form = useForm({
|
|
24
|
+
defaultValues: {
|
|
25
|
+
firstName: '',
|
|
26
|
+
lastName: '',
|
|
27
|
+
email: '',
|
|
28
|
+
},
|
|
29
|
+
shouldFocusError: false, // Disable default focus behavior for testing
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const { registerCustomFocusableElement, focusOnError } = useFieldFocusManager(
|
|
33
|
+
{
|
|
34
|
+
enabled,
|
|
35
|
+
control: form.control,
|
|
36
|
+
subscribe: form.subscribe,
|
|
37
|
+
setFocus: form.setFocus,
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Expose register function for testing
|
|
42
|
+
React.useEffect(() => {
|
|
43
|
+
if (onRegisterElement) {
|
|
44
|
+
onRegisterElement(registerCustomFocusableElement);
|
|
45
|
+
}
|
|
46
|
+
}, [registerCustomFocusableElement, onRegisterElement]);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<form onSubmit={form.handleSubmit(() => {}, focusOnError)}>
|
|
50
|
+
<input
|
|
51
|
+
{...form.register('firstName', { required: 'First name is required' })}
|
|
52
|
+
data-testid="firstName"
|
|
53
|
+
placeholder="First Name"
|
|
54
|
+
/>
|
|
55
|
+
<input
|
|
56
|
+
{...form.register('lastName', { required: 'Last name is required' })}
|
|
57
|
+
data-testid="lastName"
|
|
58
|
+
placeholder="Last Name"
|
|
59
|
+
/>
|
|
60
|
+
<input
|
|
61
|
+
{...form.register('email', { required: 'Email is required' })}
|
|
62
|
+
data-testid="email"
|
|
63
|
+
placeholder="Email"
|
|
64
|
+
/>
|
|
65
|
+
<button type="submit" data-testid="submit">
|
|
66
|
+
Submit
|
|
67
|
+
</button>
|
|
68
|
+
</form>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// FieldArray test component
|
|
73
|
+
function TestFieldArrayComponent({
|
|
74
|
+
enabled = true,
|
|
75
|
+
onRegisterElement,
|
|
76
|
+
}: {
|
|
77
|
+
enabled?: boolean;
|
|
78
|
+
onRegisterElement?: (register: any) => void;
|
|
79
|
+
}) {
|
|
80
|
+
const form = useForm({
|
|
81
|
+
defaultValues: {
|
|
82
|
+
users: [{ name: '', email: '' }],
|
|
83
|
+
items: [{ title: '', description: '' }],
|
|
84
|
+
},
|
|
85
|
+
shouldFocusError: false,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const { fields: userFields, append: appendUser } = useFieldArray({
|
|
89
|
+
control: form.control,
|
|
90
|
+
name: 'users',
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const { fields: itemFields, append: appendItem } = useFieldArray({
|
|
94
|
+
control: form.control,
|
|
95
|
+
name: 'items',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const { registerCustomFocusableElement, focusOnError } = useFieldFocusManager(
|
|
99
|
+
{
|
|
100
|
+
enabled,
|
|
101
|
+
control: form.control,
|
|
102
|
+
subscribe: form.subscribe,
|
|
103
|
+
setFocus: form.setFocus,
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
React.useEffect(() => {
|
|
108
|
+
if (onRegisterElement) {
|
|
109
|
+
onRegisterElement(registerCustomFocusableElement);
|
|
110
|
+
}
|
|
111
|
+
}, [registerCustomFocusableElement, onRegisterElement]);
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<form onSubmit={form.handleSubmit(() => {}, focusOnError)}>
|
|
115
|
+
<div data-testid="users-section">
|
|
116
|
+
{userFields.map((field, index) => (
|
|
117
|
+
<div key={field.id} data-testid={`user-${index}`}>
|
|
118
|
+
<input
|
|
119
|
+
{...form.register(`users.${index}.name`, {
|
|
120
|
+
required: 'Name is required',
|
|
121
|
+
})}
|
|
122
|
+
data-testid={`user-${index}-name`}
|
|
123
|
+
placeholder={`User ${index} Name`}
|
|
124
|
+
/>
|
|
125
|
+
<input
|
|
126
|
+
{...form.register(`users.${index}.email`, {
|
|
127
|
+
required: 'Email is required',
|
|
128
|
+
})}
|
|
129
|
+
data-testid={`user-${index}-email`}
|
|
130
|
+
placeholder={`User ${index} Email`}
|
|
131
|
+
/>
|
|
132
|
+
</div>
|
|
133
|
+
))}
|
|
134
|
+
<button
|
|
135
|
+
type="button"
|
|
136
|
+
onClick={() => appendUser({ name: '', email: '' })}
|
|
137
|
+
data-testid="add-user"
|
|
138
|
+
>
|
|
139
|
+
Add User
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div data-testid="items-section">
|
|
144
|
+
{itemFields.map((field, index) => (
|
|
145
|
+
<div key={field.id} data-testid={`item-${index}`}>
|
|
146
|
+
<input
|
|
147
|
+
{...form.register(`items.${index}.title`, {
|
|
148
|
+
required: 'Title is required',
|
|
149
|
+
})}
|
|
150
|
+
data-testid={`item-${index}-title`}
|
|
151
|
+
placeholder={`Item ${index} Title`}
|
|
152
|
+
/>
|
|
153
|
+
<input
|
|
154
|
+
{...form.register(`items.${index}.description`)}
|
|
155
|
+
data-testid={`item-${index}-description`}
|
|
156
|
+
placeholder={`Item ${index} Description`}
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
))}
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
onClick={() => appendItem({ title: '', description: '' })}
|
|
163
|
+
data-testid="add-item"
|
|
164
|
+
>
|
|
165
|
+
Add Item
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<button type="submit" data-testid="submit">
|
|
170
|
+
Submit
|
|
171
|
+
</button>
|
|
172
|
+
</form>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Nested FieldArray component
|
|
177
|
+
function TestNestedFieldArrayComponent() {
|
|
178
|
+
const form = useForm({
|
|
179
|
+
defaultValues: {
|
|
180
|
+
projects: [
|
|
181
|
+
{
|
|
182
|
+
name: '',
|
|
183
|
+
tasks: [{ title: '', assignee: '' }],
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
shouldFocusError: false,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const { fields: projectFields, append: appendProject } = useFieldArray({
|
|
191
|
+
control: form.control,
|
|
192
|
+
name: 'projects',
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const { focusOnError } = useFieldFocusManager({
|
|
196
|
+
control: form.control,
|
|
197
|
+
subscribe: form.subscribe,
|
|
198
|
+
setFocus: form.setFocus,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<form onSubmit={form.handleSubmit(() => {}, focusOnError)}>
|
|
203
|
+
{projectFields.map((projectField, projectIndex) => (
|
|
204
|
+
<div key={projectField.id} data-testid={`project-${projectIndex}`}>
|
|
205
|
+
<input
|
|
206
|
+
{...form.register(`projects.${projectIndex}.name`, {
|
|
207
|
+
required: 'Project name is required',
|
|
208
|
+
})}
|
|
209
|
+
data-testid={`project-${projectIndex}-name`}
|
|
210
|
+
placeholder={`Project ${projectIndex} Name`}
|
|
211
|
+
/>
|
|
212
|
+
|
|
213
|
+
<NestedTasksFieldArray
|
|
214
|
+
control={form.control}
|
|
215
|
+
register={form.register}
|
|
216
|
+
projectIndex={projectIndex}
|
|
217
|
+
/>
|
|
218
|
+
</div>
|
|
219
|
+
))}
|
|
220
|
+
|
|
221
|
+
<button
|
|
222
|
+
type="button"
|
|
223
|
+
onClick={() =>
|
|
224
|
+
appendProject({ name: '', tasks: [{ title: '', assignee: '' }] })
|
|
225
|
+
}
|
|
226
|
+
data-testid="add-project"
|
|
227
|
+
>
|
|
228
|
+
Add Project
|
|
229
|
+
</button>
|
|
230
|
+
|
|
231
|
+
<button type="submit" data-testid="submit">
|
|
232
|
+
Submit
|
|
233
|
+
</button>
|
|
234
|
+
</form>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function NestedTasksFieldArray({
|
|
239
|
+
control,
|
|
240
|
+
register,
|
|
241
|
+
projectIndex,
|
|
242
|
+
}: {
|
|
243
|
+
control: any;
|
|
244
|
+
register: any;
|
|
245
|
+
projectIndex: number;
|
|
246
|
+
}) {
|
|
247
|
+
const { fields: taskFields, append: appendTask } = useFieldArray({
|
|
248
|
+
control,
|
|
249
|
+
name: `projects.${projectIndex}.tasks`,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<div data-testid={`project-${projectIndex}-tasks`}>
|
|
254
|
+
{taskFields.map((taskField, taskIndex) => (
|
|
255
|
+
<div
|
|
256
|
+
key={taskField.id}
|
|
257
|
+
data-testid={`project-${projectIndex}-task-${taskIndex}`}
|
|
258
|
+
>
|
|
259
|
+
<input
|
|
260
|
+
{...register(`projects.${projectIndex}.tasks.${taskIndex}.title`, {
|
|
261
|
+
required: 'Task title is required',
|
|
262
|
+
})}
|
|
263
|
+
data-testid={`project-${projectIndex}-task-${taskIndex}-title`}
|
|
264
|
+
placeholder={`Task ${taskIndex} Title`}
|
|
265
|
+
/>
|
|
266
|
+
<input
|
|
267
|
+
{...register(
|
|
268
|
+
`projects.${projectIndex}.tasks.${taskIndex}.assignee`,
|
|
269
|
+
{
|
|
270
|
+
required: 'Assignee is required',
|
|
271
|
+
}
|
|
272
|
+
)}
|
|
273
|
+
data-testid={`project-${projectIndex}-task-${taskIndex}-assignee`}
|
|
274
|
+
placeholder={`Task ${taskIndex} Assignee`}
|
|
275
|
+
/>
|
|
276
|
+
</div>
|
|
277
|
+
))}
|
|
278
|
+
|
|
279
|
+
<button
|
|
280
|
+
type="button"
|
|
281
|
+
onClick={() => appendTask({ title: '', assignee: '' })}
|
|
282
|
+
data-testid={`add-task-${projectIndex}`}
|
|
283
|
+
>
|
|
284
|
+
Add Task
|
|
285
|
+
</button>
|
|
286
|
+
</div>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
describe('useFieldFocusManager', () => {
|
|
291
|
+
beforeEach(() => {
|
|
292
|
+
vi.clearAllMocks();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
describe('basic functionality', () => {
|
|
296
|
+
it('should return registerCustomFocusableElement function', () => {
|
|
297
|
+
const { result } = renderHook(() => {
|
|
298
|
+
const form = useForm();
|
|
299
|
+
return useFieldFocusManager({
|
|
300
|
+
control: form.control,
|
|
301
|
+
subscribe: form.subscribe,
|
|
302
|
+
setFocus: form.setFocus,
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
expect(result.current.registerCustomFocusableElement).toBeInstanceOf(
|
|
307
|
+
Function
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should be enabled by default', () => {
|
|
312
|
+
const { result } = renderHook(() => {
|
|
313
|
+
const form = useForm();
|
|
314
|
+
return useFieldFocusManager({
|
|
315
|
+
control: form.control,
|
|
316
|
+
subscribe: form.subscribe,
|
|
317
|
+
setFocus: form.setFocus,
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
expect(result.current.registerCustomFocusableElement).toBeDefined();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
describe('error focusing behavior', () => {
|
|
326
|
+
it('should focus the first field with an error in DOM order', async () => {
|
|
327
|
+
render(<TestFormComponent />);
|
|
328
|
+
|
|
329
|
+
const firstNameInput = screen.getByTestId('firstName');
|
|
330
|
+
const submitButton = screen.getByTestId('submit');
|
|
331
|
+
|
|
332
|
+
// Submit form to trigger validation errors
|
|
333
|
+
fireEvent.click(submitButton);
|
|
334
|
+
|
|
335
|
+
// First field in DOM order should be focused
|
|
336
|
+
await waitFor(() => expect(firstNameInput).toHaveFocus());
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should focus the first field with error even if not the first field in form', async () => {
|
|
340
|
+
function TestForm() {
|
|
341
|
+
const form = useForm({
|
|
342
|
+
defaultValues: {
|
|
343
|
+
firstName: 'Valid',
|
|
344
|
+
lastName: '',
|
|
345
|
+
email: '',
|
|
346
|
+
},
|
|
347
|
+
shouldFocusError: false,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const { focusOnError } = useFieldFocusManager({
|
|
351
|
+
control: form.control,
|
|
352
|
+
subscribe: form.subscribe,
|
|
353
|
+
setFocus: form.setFocus,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<form onSubmit={form.handleSubmit(() => {}, focusOnError)}>
|
|
358
|
+
<input
|
|
359
|
+
{...form.register('firstName')}
|
|
360
|
+
data-testid="firstName"
|
|
361
|
+
placeholder="First Name"
|
|
362
|
+
/>
|
|
363
|
+
<input
|
|
364
|
+
{...form.register('lastName', {
|
|
365
|
+
required: 'Last name is required',
|
|
366
|
+
})}
|
|
367
|
+
data-testid="lastName"
|
|
368
|
+
placeholder="Last Name"
|
|
369
|
+
/>
|
|
370
|
+
<input
|
|
371
|
+
{...form.register('email', { required: 'Email is required' })}
|
|
372
|
+
data-testid="email"
|
|
373
|
+
placeholder="Email"
|
|
374
|
+
/>
|
|
375
|
+
<button type="submit" data-testid="submit">
|
|
376
|
+
Submit
|
|
377
|
+
</button>
|
|
378
|
+
</form>
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
render(<TestForm />);
|
|
383
|
+
|
|
384
|
+
const lastNameInput = screen.getByTestId('lastName');
|
|
385
|
+
const submitButton = screen.getByTestId('submit');
|
|
386
|
+
|
|
387
|
+
// Submit form to trigger validation errors
|
|
388
|
+
fireEvent.click(submitButton);
|
|
389
|
+
|
|
390
|
+
// Wait for focus to be applied
|
|
391
|
+
await act(async () => {
|
|
392
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// lastName should be focused as it's the first field with an error
|
|
396
|
+
expect(lastNameInput).toHaveFocus();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should not focus any field when there are no errors', async () => {
|
|
400
|
+
function TestForm() {
|
|
401
|
+
const form = useForm({
|
|
402
|
+
defaultValues: {
|
|
403
|
+
firstName: 'John',
|
|
404
|
+
lastName: 'Doe',
|
|
405
|
+
email: 'john@example.com',
|
|
406
|
+
},
|
|
407
|
+
shouldFocusError: false,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const { focusOnError } = useFieldFocusManager({
|
|
411
|
+
control: form.control,
|
|
412
|
+
subscribe: form.subscribe,
|
|
413
|
+
setFocus: form.setFocus,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
return (
|
|
417
|
+
<form onSubmit={form.handleSubmit(() => {}, focusOnError)}>
|
|
418
|
+
<input
|
|
419
|
+
{...form.register('firstName', {
|
|
420
|
+
required: 'First name is required',
|
|
421
|
+
})}
|
|
422
|
+
data-testid="input"
|
|
423
|
+
placeholder="First Name"
|
|
424
|
+
/>
|
|
425
|
+
<input
|
|
426
|
+
{...form.register('lastName', {
|
|
427
|
+
required: 'Last name is required',
|
|
428
|
+
})}
|
|
429
|
+
data-testid="input"
|
|
430
|
+
placeholder="Last Name"
|
|
431
|
+
/>
|
|
432
|
+
<input
|
|
433
|
+
{...form.register('email', { required: 'Email is required' })}
|
|
434
|
+
data-testid="input"
|
|
435
|
+
placeholder="Email"
|
|
436
|
+
/>
|
|
437
|
+
<button type="submit" data-testid="submit">
|
|
438
|
+
Submit
|
|
439
|
+
</button>
|
|
440
|
+
</form>
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
render(<TestForm />);
|
|
445
|
+
|
|
446
|
+
const submitButton = screen.getByTestId('submit');
|
|
447
|
+
|
|
448
|
+
// Submit form - should not trigger any focus since all fields are valid
|
|
449
|
+
fireEvent.click(submitButton);
|
|
450
|
+
|
|
451
|
+
// Wait for any potential focus changes
|
|
452
|
+
await act(async () => {
|
|
453
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// No input should be focused (submit button should retain focus)
|
|
457
|
+
for (const input of screen.getAllByTestId('input')) {
|
|
458
|
+
expect(input).not.toHaveFocus();
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('should focus fields in correct DOM order regardless of registration order', async () => {
|
|
463
|
+
function SimpleOrderTest() {
|
|
464
|
+
const form = useForm({
|
|
465
|
+
defaultValues: {
|
|
466
|
+
field1: 'valid',
|
|
467
|
+
field2: '',
|
|
468
|
+
field3: '',
|
|
469
|
+
},
|
|
470
|
+
shouldFocusError: false,
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
const { focusOnError } = useFieldFocusManager({
|
|
474
|
+
control: form.control,
|
|
475
|
+
subscribe: form.subscribe,
|
|
476
|
+
setFocus: form.setFocus,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
return (
|
|
480
|
+
<form onSubmit={form.handleSubmit(() => {}, focusOnError)}>
|
|
481
|
+
<input {...form.register('field1')} data-testid="field1" />
|
|
482
|
+
<input
|
|
483
|
+
{...form.register('field2', { required: 'Required' })}
|
|
484
|
+
data-testid="field2"
|
|
485
|
+
/>
|
|
486
|
+
<input
|
|
487
|
+
{...form.register('field3', { required: 'Required' })}
|
|
488
|
+
data-testid="field3"
|
|
489
|
+
/>
|
|
490
|
+
<button type="submit" data-testid="submit">
|
|
491
|
+
Submit
|
|
492
|
+
</button>
|
|
493
|
+
</form>
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
render(<SimpleOrderTest />);
|
|
498
|
+
|
|
499
|
+
const submitButton = screen.getByTestId('submit');
|
|
500
|
+
fireEvent.click(submitButton);
|
|
501
|
+
|
|
502
|
+
await act(async () => {
|
|
503
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// field2 should be focused as it's the first field with an error in DOM order
|
|
507
|
+
const field2Input = screen.getByTestId('field2');
|
|
508
|
+
expect(field2Input).toHaveFocus();
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
describe('FieldArray support', () => {
|
|
513
|
+
it('should focus first field array field with error', async () => {
|
|
514
|
+
render(<TestFieldArrayComponent />);
|
|
515
|
+
|
|
516
|
+
const submitButton = screen.getByTestId('submit');
|
|
517
|
+
|
|
518
|
+
// Submit form to trigger validation errors on the first user's name field
|
|
519
|
+
fireEvent.click(submitButton);
|
|
520
|
+
|
|
521
|
+
await act(async () => {
|
|
522
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// First field in first user should be focused (users.0.name)
|
|
526
|
+
const firstUserNameInput = screen.getByTestId('user-0-name');
|
|
527
|
+
expect(firstUserNameInput).toHaveFocus();
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('should focus field arrays in DOM order', async () => {
|
|
531
|
+
render(<TestFieldArrayComponent />);
|
|
532
|
+
|
|
533
|
+
const addUserButton = screen.getByTestId('add-user');
|
|
534
|
+
|
|
535
|
+
// Add another user (but don't add items yet)
|
|
536
|
+
fireEvent.click(addUserButton);
|
|
537
|
+
|
|
538
|
+
// Fill in the first user's fields to make them valid
|
|
539
|
+
const firstUserName = screen.getByTestId('user-0-name');
|
|
540
|
+
const firstUserEmail = screen.getByTestId('user-0-email');
|
|
541
|
+
|
|
542
|
+
fireEvent.change(firstUserName, { target: { value: 'John' } });
|
|
543
|
+
fireEvent.change(firstUserEmail, {
|
|
544
|
+
target: { value: 'john@example.com' },
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// Also fill in the first item's title to make it valid
|
|
548
|
+
const firstItemTitle = screen.getByTestId('item-0-title');
|
|
549
|
+
fireEvent.change(firstItemTitle, { target: { value: 'Valid Title' } });
|
|
550
|
+
|
|
551
|
+
const submitButton = screen.getByTestId('submit');
|
|
552
|
+
fireEvent.click(submitButton);
|
|
553
|
+
|
|
554
|
+
await act(async () => {
|
|
555
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
// Should focus the second user's name field (first error in DOM order)
|
|
559
|
+
const secondUserNameInput = screen.getByTestId('user-1-name');
|
|
560
|
+
expect(secondUserNameInput).toHaveFocus();
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it('should handle dynamically added field array items', async () => {
|
|
564
|
+
render(<TestFieldArrayComponent />);
|
|
565
|
+
|
|
566
|
+
const addUserButton = screen.getByTestId('add-user');
|
|
567
|
+
const addItemButton = screen.getByTestId('add-item');
|
|
568
|
+
|
|
569
|
+
// Add multiple users and items
|
|
570
|
+
fireEvent.click(addUserButton);
|
|
571
|
+
fireEvent.click(addUserButton);
|
|
572
|
+
fireEvent.click(addItemButton);
|
|
573
|
+
|
|
574
|
+
// Verify elements were added
|
|
575
|
+
expect(screen.getByTestId('user-0-name')).toBeInTheDocument();
|
|
576
|
+
expect(screen.getByTestId('user-1-name')).toBeInTheDocument();
|
|
577
|
+
expect(screen.getByTestId('user-2-name')).toBeInTheDocument();
|
|
578
|
+
expect(screen.getByTestId('item-0-title')).toBeInTheDocument();
|
|
579
|
+
|
|
580
|
+
const submitButton = screen.getByTestId('submit');
|
|
581
|
+
fireEvent.click(submitButton);
|
|
582
|
+
|
|
583
|
+
await act(async () => {
|
|
584
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// Should focus the first field with an error (first user's name)
|
|
588
|
+
const firstUserNameInput = screen.getByTestId('user-0-name');
|
|
589
|
+
expect(firstUserNameInput).toHaveFocus();
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('should focus items array fields when users are valid', async () => {
|
|
593
|
+
render(<TestFieldArrayComponent />);
|
|
594
|
+
|
|
595
|
+
const addItemButton = screen.getByTestId('add-item');
|
|
596
|
+
fireEvent.click(addItemButton);
|
|
597
|
+
|
|
598
|
+
// Fill in user fields to make them valid
|
|
599
|
+
const userNameInput = screen.getByTestId('user-0-name');
|
|
600
|
+
const userEmailInput = screen.getByTestId('user-0-email');
|
|
601
|
+
|
|
602
|
+
fireEvent.change(userNameInput, { target: { value: 'John' } });
|
|
603
|
+
fireEvent.change(userEmailInput, {
|
|
604
|
+
target: { value: 'john@example.com' },
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
const submitButton = screen.getByTestId('submit');
|
|
608
|
+
fireEvent.click(submitButton);
|
|
609
|
+
|
|
610
|
+
await act(async () => {
|
|
611
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// Should focus the first item's title field (first error in items array)
|
|
615
|
+
const firstItemTitleInput = screen.getByTestId('item-0-title');
|
|
616
|
+
expect(firstItemTitleInput).toHaveFocus();
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
it('should handle mixed field array and regular field errors', async () => {
|
|
620
|
+
function MixedFieldsForm() {
|
|
621
|
+
const form = useForm({
|
|
622
|
+
defaultValues: {
|
|
623
|
+
regularField: '',
|
|
624
|
+
users: [{ name: '', email: '' }],
|
|
625
|
+
},
|
|
626
|
+
shouldFocusError: false,
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
const { fields } = useFieldArray({
|
|
630
|
+
control: form.control,
|
|
631
|
+
name: 'users',
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
const { focusOnError } = useFieldFocusManager({
|
|
635
|
+
control: form.control,
|
|
636
|
+
subscribe: form.subscribe,
|
|
637
|
+
setFocus: form.setFocus,
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
return (
|
|
641
|
+
<form onSubmit={form.handleSubmit(() => {}, focusOnError)}>
|
|
642
|
+
<input
|
|
643
|
+
{...form.register('regularField', {
|
|
644
|
+
required: 'Regular field is required',
|
|
645
|
+
})}
|
|
646
|
+
data-testid="regular-field"
|
|
647
|
+
placeholder="Regular Field"
|
|
648
|
+
/>
|
|
649
|
+
|
|
650
|
+
{fields.map((field, index) => (
|
|
651
|
+
<div key={field.id}>
|
|
652
|
+
<input
|
|
653
|
+
{...form.register(`users.${index}.name`, {
|
|
654
|
+
required: 'Name is required',
|
|
655
|
+
})}
|
|
656
|
+
data-testid={`user-${index}-name`}
|
|
657
|
+
placeholder={`User ${index} Name`}
|
|
658
|
+
/>
|
|
659
|
+
</div>
|
|
660
|
+
))}
|
|
661
|
+
|
|
662
|
+
<button type="submit" data-testid="submit">
|
|
663
|
+
Submit
|
|
664
|
+
</button>
|
|
665
|
+
</form>
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
render(<MixedFieldsForm />);
|
|
670
|
+
|
|
671
|
+
const submitButton = screen.getByTestId('submit');
|
|
672
|
+
fireEvent.click(submitButton);
|
|
673
|
+
|
|
674
|
+
await act(async () => {
|
|
675
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
// Should focus the regular field first (appears first in DOM)
|
|
679
|
+
const regularFieldInput = screen.getByTestId('regular-field');
|
|
680
|
+
expect(regularFieldInput).toHaveFocus();
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
describe('nested FieldArrays', () => {
|
|
685
|
+
it('should focus nested field array fields', async () => {
|
|
686
|
+
render(<TestNestedFieldArrayComponent />);
|
|
687
|
+
|
|
688
|
+
const submitButton = screen.getByTestId('submit');
|
|
689
|
+
fireEvent.click(submitButton);
|
|
690
|
+
|
|
691
|
+
await act(async () => {
|
|
692
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// Should focus the project name field first
|
|
696
|
+
const projectNameInput = screen.getByTestId('project-0-name');
|
|
697
|
+
expect(projectNameInput).toHaveFocus();
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
it('should focus nested task fields when project name is valid', async () => {
|
|
701
|
+
render(<TestNestedFieldArrayComponent />);
|
|
702
|
+
|
|
703
|
+
// Fill in project name to make it valid
|
|
704
|
+
const projectNameInput = screen.getByTestId('project-0-name');
|
|
705
|
+
fireEvent.change(projectNameInput, { target: { value: 'Project 1' } });
|
|
706
|
+
|
|
707
|
+
const submitButton = screen.getByTestId('submit');
|
|
708
|
+
fireEvent.click(submitButton);
|
|
709
|
+
|
|
710
|
+
await act(async () => {
|
|
711
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
// Should focus the first task's title field
|
|
715
|
+
const taskTitleInput = screen.getByTestId('project-0-task-0-title');
|
|
716
|
+
expect(taskTitleInput).toHaveFocus();
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
it('should handle adding nested field arrays dynamically', async () => {
|
|
720
|
+
render(<TestNestedFieldArrayComponent />);
|
|
721
|
+
|
|
722
|
+
const addProjectButton = screen.getByTestId('add-project');
|
|
723
|
+
const addTaskButton = screen.getByTestId('add-task-0');
|
|
724
|
+
|
|
725
|
+
// Add another project and task
|
|
726
|
+
fireEvent.click(addProjectButton);
|
|
727
|
+
fireEvent.click(addTaskButton);
|
|
728
|
+
|
|
729
|
+
// Verify elements were added
|
|
730
|
+
expect(screen.getByTestId('project-0-name')).toBeInTheDocument();
|
|
731
|
+
expect(screen.getByTestId('project-1-name')).toBeInTheDocument();
|
|
732
|
+
expect(screen.getByTestId('project-0-task-0-title')).toBeInTheDocument();
|
|
733
|
+
expect(screen.getByTestId('project-0-task-1-title')).toBeInTheDocument();
|
|
734
|
+
|
|
735
|
+
const submitButton = screen.getByTestId('submit');
|
|
736
|
+
fireEvent.click(submitButton);
|
|
737
|
+
|
|
738
|
+
await act(async () => {
|
|
739
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
// Should focus the first project's name field
|
|
743
|
+
const firstProjectNameInput = screen.getByTestId('project-0-name');
|
|
744
|
+
expect(firstProjectNameInput).toHaveFocus();
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
it('should focus deeply nested fields in correct order', async () => {
|
|
748
|
+
render(<TestNestedFieldArrayComponent />);
|
|
749
|
+
|
|
750
|
+
// Fill in first project name and first task title to test deep nesting
|
|
751
|
+
const projectNameInput = screen.getByTestId('project-0-name');
|
|
752
|
+
const taskTitleInput = screen.getByTestId('project-0-task-0-title');
|
|
753
|
+
|
|
754
|
+
fireEvent.change(projectNameInput, { target: { value: 'Project 1' } });
|
|
755
|
+
fireEvent.change(taskTitleInput, { target: { value: 'Task 1' } });
|
|
756
|
+
|
|
757
|
+
const submitButton = screen.getByTestId('submit');
|
|
758
|
+
fireEvent.click(submitButton);
|
|
759
|
+
|
|
760
|
+
await act(async () => {
|
|
761
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
// Should focus the first task's assignee field (next field with error)
|
|
765
|
+
const taskAssigneeInput = screen.getByTestId('project-0-task-0-assignee');
|
|
766
|
+
expect(taskAssigneeInput).toHaveFocus();
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
describe('custom focusable elements', () => {
|
|
771
|
+
it('should register and focus custom elements', async () => {
|
|
772
|
+
function TestForm() {
|
|
773
|
+
const form = useForm({
|
|
774
|
+
defaultValues: {
|
|
775
|
+
firstName: '',
|
|
776
|
+
},
|
|
777
|
+
shouldFocusError: false,
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
const { registerCustomFocusableElement, focusOnError } =
|
|
781
|
+
useFieldFocusManager({
|
|
782
|
+
control: form.control,
|
|
783
|
+
subscribe: form.subscribe,
|
|
784
|
+
setFocus: form.setFocus,
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
return (
|
|
788
|
+
<form onSubmit={form.handleSubmit(() => {}, focusOnError)}>
|
|
789
|
+
<input
|
|
790
|
+
{...form.register('firstName', {
|
|
791
|
+
required: 'First name is required',
|
|
792
|
+
})}
|
|
793
|
+
data-testid="firstName"
|
|
794
|
+
placeholder="First Name"
|
|
795
|
+
/>
|
|
796
|
+
<div
|
|
797
|
+
data-testid="custom-element"
|
|
798
|
+
tabIndex={0}
|
|
799
|
+
ref={(el) => {
|
|
800
|
+
if (el) {
|
|
801
|
+
registerCustomFocusableElement('customField', el);
|
|
802
|
+
}
|
|
803
|
+
}}
|
|
804
|
+
>
|
|
805
|
+
Custom focusable element
|
|
806
|
+
</div>
|
|
807
|
+
<button type="submit" data-testid="submit">
|
|
808
|
+
Submit
|
|
809
|
+
</button>
|
|
810
|
+
</form>
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
render(<TestForm />);
|
|
815
|
+
|
|
816
|
+
const submitButton = screen.getByTestId('submit');
|
|
817
|
+
|
|
818
|
+
// Submit form to trigger validation errors
|
|
819
|
+
fireEvent.click(submitButton);
|
|
820
|
+
|
|
821
|
+
// Wait for focus to be applied
|
|
822
|
+
await act(async () => {
|
|
823
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
// First field should still be focused since it has an error
|
|
827
|
+
const firstNameInput = screen.getByTestId('firstName');
|
|
828
|
+
expect(firstNameInput).toHaveFocus();
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
it('should return an unregister function', () => {
|
|
832
|
+
let registerFunction: any;
|
|
833
|
+
|
|
834
|
+
render(
|
|
835
|
+
<TestFormComponent
|
|
836
|
+
onRegisterElement={(register) => {
|
|
837
|
+
registerFunction = register;
|
|
838
|
+
}}
|
|
839
|
+
/>
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
const mockElement = document.createElement('div');
|
|
843
|
+
const unregister = registerFunction('testField', mockElement);
|
|
844
|
+
|
|
845
|
+
expect(unregister).toBeInstanceOf(Function);
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
it('should register custom elements for field array items', async () => {
|
|
849
|
+
function TestFormWithCustomElements() {
|
|
850
|
+
const form = useForm({
|
|
851
|
+
defaultValues: {
|
|
852
|
+
items: [{ customField: '' }],
|
|
853
|
+
},
|
|
854
|
+
shouldFocusError: false,
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
const { fields } = useFieldArray({
|
|
858
|
+
control: form.control,
|
|
859
|
+
name: 'items',
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
const { registerCustomFocusableElement, focusOnError } =
|
|
863
|
+
useFieldFocusManager({
|
|
864
|
+
control: form.control,
|
|
865
|
+
subscribe: form.subscribe,
|
|
866
|
+
setFocus: form.setFocus,
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
return (
|
|
870
|
+
<form onSubmit={form.handleSubmit(() => {}, focusOnError)}>
|
|
871
|
+
{fields.map((field, index) => (
|
|
872
|
+
<div key={field.id}>
|
|
873
|
+
<div
|
|
874
|
+
data-testid={`custom-element-${index}`}
|
|
875
|
+
tabIndex={0}
|
|
876
|
+
ref={(el) => {
|
|
877
|
+
if (el) {
|
|
878
|
+
registerCustomFocusableElement(
|
|
879
|
+
`items.${index}.customField`,
|
|
880
|
+
el
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
}}
|
|
884
|
+
>
|
|
885
|
+
Custom element {index}
|
|
886
|
+
</div>
|
|
887
|
+
</div>
|
|
888
|
+
))}
|
|
889
|
+
<button type="submit" data-testid="submit">
|
|
890
|
+
Submit
|
|
891
|
+
</button>
|
|
892
|
+
</form>
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
render(<TestFormWithCustomElements />);
|
|
897
|
+
|
|
898
|
+
const customElement = screen.getByTestId('custom-element-0');
|
|
899
|
+
expect(customElement).toBeInTheDocument();
|
|
900
|
+
|
|
901
|
+
// The custom element should be registered and available for focusing
|
|
902
|
+
expect(customElement.tabIndex).toBe(0);
|
|
903
|
+
});
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
describe('enabled/disabled behavior', () => {
|
|
907
|
+
it('should not focus fields when disabled', async () => {
|
|
908
|
+
render(<TestFormComponent enabled={false} />);
|
|
909
|
+
|
|
910
|
+
const firstNameInput = screen.getByTestId('firstName');
|
|
911
|
+
const submitButton = screen.getByTestId('submit');
|
|
912
|
+
|
|
913
|
+
// Submit form to trigger validation errors
|
|
914
|
+
fireEvent.click(submitButton);
|
|
915
|
+
|
|
916
|
+
// Wait for any potential focus changes
|
|
917
|
+
await act(async () => {
|
|
918
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
// No field should be focused since the hook is disabled
|
|
922
|
+
expect(firstNameInput).not.toHaveFocus();
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
it('should not focus field array fields when disabled', async () => {
|
|
926
|
+
render(<TestFieldArrayComponent enabled={false} />);
|
|
927
|
+
|
|
928
|
+
const submitButton = screen.getByTestId('submit');
|
|
929
|
+
fireEvent.click(submitButton);
|
|
930
|
+
|
|
931
|
+
await act(async () => {
|
|
932
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
// No field should be focused since the hook is disabled
|
|
936
|
+
const firstUserNameInput = screen.getByTestId('user-0-name');
|
|
937
|
+
expect(firstUserNameInput).not.toHaveFocus();
|
|
938
|
+
});
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
describe('edge cases', () => {
|
|
942
|
+
it('should handle form with no fields', () => {
|
|
943
|
+
function EmptyForm() {
|
|
944
|
+
const form = useForm();
|
|
945
|
+
useFieldFocusManager({
|
|
946
|
+
control: form.control,
|
|
947
|
+
subscribe: form.subscribe,
|
|
948
|
+
setFocus: form.setFocus,
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
return (
|
|
952
|
+
<form>
|
|
953
|
+
<button type="submit">Submit</button>
|
|
954
|
+
</form>
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
expect(() => render(<EmptyForm />)).not.toThrow();
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
it('should handle unregistering custom elements', () => {
|
|
962
|
+
let registerFunction: any;
|
|
963
|
+
|
|
964
|
+
render(
|
|
965
|
+
<TestFormComponent
|
|
966
|
+
onRegisterElement={(register) => {
|
|
967
|
+
registerFunction = register;
|
|
968
|
+
}}
|
|
969
|
+
/>
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
const mockElement = document.createElement('div');
|
|
973
|
+
const unregister = registerFunction('testField', mockElement);
|
|
974
|
+
|
|
975
|
+
// Should not throw when unregistering
|
|
976
|
+
expect(() => unregister()).not.toThrow();
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
it('should handle empty field arrays', async () => {
|
|
980
|
+
function EmptyFieldArrayForm() {
|
|
981
|
+
const form = useForm({
|
|
982
|
+
defaultValues: {
|
|
983
|
+
items: [],
|
|
984
|
+
},
|
|
985
|
+
shouldFocusError: false,
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
const { fields } = useFieldArray({
|
|
989
|
+
control: form.control,
|
|
990
|
+
// @ts-expect-error it expects `never`
|
|
991
|
+
name: 'items',
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
useFieldFocusManager({
|
|
995
|
+
control: form.control,
|
|
996
|
+
subscribe: form.subscribe,
|
|
997
|
+
setFocus: form.setFocus,
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
return (
|
|
1001
|
+
<form onSubmit={form.handleSubmit(() => {})}>
|
|
1002
|
+
{fields.map((field, index) => (
|
|
1003
|
+
<input
|
|
1004
|
+
key={field.id}
|
|
1005
|
+
{...form.register(`items.${index}.title` as any)}
|
|
1006
|
+
data-testid={`item-${index}-title`}
|
|
1007
|
+
/>
|
|
1008
|
+
))}
|
|
1009
|
+
<button type="submit" data-testid="submit">
|
|
1010
|
+
Submit
|
|
1011
|
+
</button>
|
|
1012
|
+
</form>
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
render(<EmptyFieldArrayForm />);
|
|
1017
|
+
|
|
1018
|
+
const submitButton = screen.getByTestId('submit');
|
|
1019
|
+
|
|
1020
|
+
// Should not throw when submitting form with empty field arrays
|
|
1021
|
+
expect(() => fireEvent.click(submitButton)).not.toThrow();
|
|
1022
|
+
|
|
1023
|
+
await act(async () => {
|
|
1024
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
// No errors should occur and no elements should be focused
|
|
1028
|
+
expect(screen.queryByTestId('item-0-title')).not.toBeInTheDocument();
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
it('should handle field arrays with non-sequential indices', async () => {
|
|
1032
|
+
function NonSequentialFieldArrayForm() {
|
|
1033
|
+
const form = useForm({
|
|
1034
|
+
defaultValues: {
|
|
1035
|
+
items: { 0: { title: '' }, 2: { title: '' } },
|
|
1036
|
+
},
|
|
1037
|
+
shouldFocusError: false,
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
const { focusOnError } = useFieldFocusManager({
|
|
1041
|
+
control: form.control,
|
|
1042
|
+
subscribe: form.subscribe,
|
|
1043
|
+
setFocus: form.setFocus,
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
return (
|
|
1047
|
+
<form onSubmit={form.handleSubmit(() => {}, focusOnError)}>
|
|
1048
|
+
<input
|
|
1049
|
+
{...form.register('items.0.title' as any, {
|
|
1050
|
+
required: 'Title required',
|
|
1051
|
+
})}
|
|
1052
|
+
data-testid="item-0-title"
|
|
1053
|
+
/>
|
|
1054
|
+
<input
|
|
1055
|
+
{...form.register('items.2.title' as any, {
|
|
1056
|
+
required: 'Title required',
|
|
1057
|
+
})}
|
|
1058
|
+
data-testid="item-2-title"
|
|
1059
|
+
/>
|
|
1060
|
+
<button type="submit" data-testid="submit">
|
|
1061
|
+
Submit
|
|
1062
|
+
</button>
|
|
1063
|
+
</form>
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
render(<NonSequentialFieldArrayForm />);
|
|
1068
|
+
|
|
1069
|
+
const submitButton = screen.getByTestId('submit');
|
|
1070
|
+
fireEvent.click(submitButton);
|
|
1071
|
+
|
|
1072
|
+
// Should focus the first field in DOM order
|
|
1073
|
+
await waitFor(() => {
|
|
1074
|
+
const firstItemInput = screen.getByTestId('item-0-title');
|
|
1075
|
+
expect(firstItemInput).toHaveFocus();
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
});
|
|
1079
|
+
});
|