envoc-form 2.0.1-1 → 2.0.1-13

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.
Files changed (155) hide show
  1. package/README.md +7 -7
  2. package/dist/css/envoc-form-styles.css +43 -5
  3. package/dist/css/envoc-form-styles.css.map +1 -1
  4. package/es/AddressInput/AddressInput.js +8 -7
  5. package/es/ConfirmBaseForm/ConfirmBaseForm.js +3 -2
  6. package/es/ConfirmDeleteForm/ConfirmDeleteForm.js +3 -2
  7. package/es/DatePickerInput/DatePickerInput.js +3 -2
  8. package/es/FileInput/DefaultFileList.js +36 -0
  9. package/es/FileInput/DropzoneFileInput.js +58 -0
  10. package/es/FileInput/FileInput.js +31 -9
  11. package/es/FileInput/index.js +2 -1
  12. package/es/Form/Form.js +11 -33
  13. package/es/Form/FormBasedPreventNavigation.js +1 -1
  14. package/es/FormGroupWrapper.js +2 -1
  15. package/es/FormInput/FormInput.js +29 -10
  16. package/es/FormInputArray/FormInputArray.js +39 -24
  17. package/es/IconInput.js +2 -1
  18. package/es/ReactSelectField/ReactSelectField.js +6 -3
  19. package/es/SubmitFormButton.js +2 -1
  20. package/es/__Tests__/FormTestBase.js +5 -2
  21. package/es/normalizers.js +10 -5
  22. package/es/useStandardFormInput.js +4 -2
  23. package/es/utils/index.js +3 -0
  24. package/es/utils/objectContainsNonSerializableProperty.js +16 -0
  25. package/es/utils/objectToFormData.js +65 -0
  26. package/es/utils/typeChecks.js +25 -0
  27. package/lib/AddressInput/AddressInput.js +15 -9
  28. package/lib/ConfirmBaseForm/ConfirmBaseForm.js +4 -2
  29. package/lib/ConfirmDeleteForm/ConfirmDeleteForm.js +6 -4
  30. package/lib/DatePickerInput/DatePickerInput.js +4 -2
  31. package/lib/FileInput/DefaultFileList.js +47 -0
  32. package/lib/FileInput/DropzoneFileInput.js +75 -0
  33. package/lib/FileInput/FileInput.js +39 -10
  34. package/lib/FileInput/index.js +13 -3
  35. package/lib/Form/Form.js +19 -39
  36. package/lib/Form/FormBasedPreventNavigation.js +7 -3
  37. package/lib/FormGroupWrapper.js +3 -1
  38. package/lib/FormInput/FormInput.js +31 -11
  39. package/lib/FormInputArray/FormInputArray.js +47 -26
  40. package/lib/FormSection.js +5 -1
  41. package/lib/IconInput.js +3 -1
  42. package/lib/ReactSelectField/ReactSelectField.js +13 -5
  43. package/lib/ReactSelectField/index.js +6 -2
  44. package/lib/SubmitFormButton.js +3 -1
  45. package/lib/__Tests__/FormTestBase.js +6 -2
  46. package/lib/index.js +7 -3
  47. package/lib/normalizers.js +10 -5
  48. package/lib/useStandardFormInput.js +5 -2
  49. package/lib/utils/index.js +23 -0
  50. package/lib/utils/objectContainsNonSerializableProperty.js +24 -0
  51. package/lib/utils/objectToFormData.js +73 -0
  52. package/lib/utils/typeChecks.js +58 -0
  53. package/lib/validators/index.js +5 -1
  54. package/package.json +99 -90
  55. package/src/AddressInput/AddesssInput.test.js +23 -23
  56. package/src/AddressInput/AddressInput.js +73 -73
  57. package/src/AddressInput/UsStates.js +53 -53
  58. package/src/AddressInput/__snapshots__/AddesssInput.test.js.snap +207 -207
  59. package/src/AddressInput/index.js +2 -2
  60. package/src/BoolInput/BoolInput.js +7 -7
  61. package/src/BoolInput/BoolInput.test.js +23 -23
  62. package/src/BoolInput/InlineBoolInput.js +7 -7
  63. package/src/BoolInput/__snapshots__/BoolInput.test.js.snap +89 -89
  64. package/src/BoolInput/boolOptions.js +6 -6
  65. package/src/BoolInput/index.js +4 -4
  66. package/src/ConfirmBaseForm/ConfirmBaseForm.js +37 -37
  67. package/src/ConfirmBaseForm/ConfirmBaseForm.test.js +14 -14
  68. package/src/ConfirmBaseForm/__snapshots__/ConfirmBaseForm.test.js.snap +23 -23
  69. package/src/ConfirmBaseForm/index.js +1 -1
  70. package/src/ConfirmDeleteForm/ConfirmDeleteForm.js +39 -39
  71. package/src/ConfirmDeleteForm/ConfirmDeleteForm.test.js +24 -24
  72. package/src/ConfirmDeleteForm/__snapshots__/ConfirmDeleteForm.test.js.snap +25 -25
  73. package/src/ConfirmDeleteForm/index.js +1 -1
  74. package/src/DatePickerInput/DatePickerInput.js +46 -46
  75. package/src/DatePickerInput/DatePickerInput.test.js +74 -74
  76. package/src/DatePickerInput/__snapshots__/DatePickerInput.test.js.snap +134 -131
  77. package/src/DatePickerInput/date-picker-input.scss +42 -42
  78. package/src/DatePickerInput/index.js +3 -3
  79. package/src/ErrorScrollTarget.js +6 -6
  80. package/src/FileInput/DefaultFileList.js +39 -0
  81. package/src/FileInput/DropzoneFileInput.js +56 -0
  82. package/src/FileInput/DropzoneFileInput.test.js +24 -0
  83. package/src/FileInput/FileInput.js +77 -49
  84. package/src/FileInput/FileInput.test.js +24 -15
  85. package/src/FileInput/__snapshots__/DropzoneFileInput.test.js.snap +57 -0
  86. package/src/FileInput/__snapshots__/FileInput.test.js.snap +58 -22
  87. package/src/FileInput/file-input.scss +58 -17
  88. package/src/FileInput/index.js +4 -3
  89. package/src/Form/FocusError.js +48 -48
  90. package/src/Form/Form.js +139 -161
  91. package/src/Form/Form.test.js +23 -23
  92. package/src/Form/FormBasedPreventNavigation.js +25 -25
  93. package/src/Form/ServerErrorContext.js +7 -7
  94. package/src/Form/__snapshots__/Form.test.js.snap +9 -9
  95. package/src/Form/index.js +3 -3
  96. package/src/FormGroup.js +30 -30
  97. package/src/FormGroupWrapper.js +28 -28
  98. package/src/FormInput/FormInput.js +144 -136
  99. package/src/FormInput/FormInput.test.js +66 -66
  100. package/src/FormInput/__snapshots__/FormInput.test.js.snap +323 -313
  101. package/src/FormInput/form-input.scss +9 -9
  102. package/src/FormInput/index.js +2 -2
  103. package/src/FormInputArray/FormInputArray.js +224 -210
  104. package/src/FormInputArray/FormInputArray.test.js +108 -59
  105. package/src/FormInputArray/__snapshots__/FormInputArray.test.js.snap +52 -40
  106. package/src/FormInputArray/form-input-array.scss +13 -8
  107. package/src/FormInputArray/index.js +2 -2
  108. package/src/FormSection.js +13 -13
  109. package/src/IconInput.js +31 -31
  110. package/src/InlineFormInput/InlineFormInput.js +6 -6
  111. package/src/InlineFormInput/InlineFormInput.test.js +23 -23
  112. package/src/InlineFormInput/__snapshots__/InlineFormInput.test.js.snap +26 -26
  113. package/src/InlineFormInput/index.js +3 -3
  114. package/src/InlineFormInput/inline-form-input.scss +3 -3
  115. package/src/MoneyInput/InlineMoneyInput.js +7 -7
  116. package/src/MoneyInput/MoneyInput.js +7 -7
  117. package/src/MoneyInput/MoneyInputs.test.js +43 -43
  118. package/src/MoneyInput/__snapshots__/MoneyInputs.test.js.snap +81 -81
  119. package/src/MoneyInput/index.js +4 -4
  120. package/src/MoneyInput/money-input.scss +3 -3
  121. package/src/MoneyInput/moneyInputProps.js +12 -12
  122. package/src/NestedFormFieldContext.js +6 -6
  123. package/src/ReactSelectField/ReactSelectField.js +122 -120
  124. package/src/ReactSelectField/index.js +6 -6
  125. package/src/ReactSelectField/react-select-field.scss +5 -5
  126. package/src/StandardFormActions.js +27 -27
  127. package/src/SubmitFormButton.js +28 -28
  128. package/src/__Tests__/FormTestBase.js +14 -11
  129. package/src/__Tests__/IconInput.test.js +23 -23
  130. package/src/__Tests__/StandardFormActions.test.js +23 -23
  131. package/src/__Tests__/SubmitFormButton.test.js +23 -23
  132. package/src/__Tests__/__snapshots__/IconInput.test.js.snap +38 -38
  133. package/src/__Tests__/__snapshots__/StandardFormActions.test.js.snap +25 -25
  134. package/src/__Tests__/__snapshots__/SubmitFormButton.test.js.snap +18 -18
  135. package/src/__Tests__/index.js +2 -2
  136. package/src/_variables.scss +11 -11
  137. package/src/index.js +33 -33
  138. package/src/normalizers.js +42 -32
  139. package/src/selectors.js +3 -3
  140. package/src/styles.scss +7 -7
  141. package/src/useStandardFormInput.js +118 -118
  142. package/src/utils/index.js +3 -0
  143. package/src/utils/objectContainsNonSerializableProperty.js +15 -0
  144. package/src/utils/objectContainsNonSerializableProperty.test.js +49 -0
  145. package/src/utils/objectToFormData.js +89 -0
  146. package/src/utils/objectToFormData.test.js +76 -0
  147. package/src/utils/typeChecks.js +18 -0
  148. package/src/validators/index.js +2 -2
  149. package/src/validators/validators.js +93 -93
  150. package/src/validators/validators.test.js +79 -79
  151. package/CHANGELOG.json +0 -95
  152. package/CHANGELOG.md +0 -58
  153. package/es/FormInput/utilities.js +0 -71
  154. package/lib/FormInput/utilities.js +0 -86
  155. package/src/FormInput/utilities.js +0 -26
@@ -1,49 +1,77 @@
1
- import React, { useEffect, useRef } from 'react';
2
-
3
- export default function FileInput({ className, onChange, value, ...props }) {
4
- const inputRef = useRef();
5
- useEffect(() => {
6
- if (!value && inputRef.current) {
7
- inputRef.current.value = null;
8
- }
9
- }, [value]);
10
- return (
11
- <div
12
- className={`custom-file file-input ${className || ''} ${
13
- props.disabled ? 'disabled' : ''
14
- }`}>
15
- <input
16
- ref={inputRef}
17
- type="file"
18
- className="custom-file-input"
19
- onChange={handleChange}
20
- {...props}
21
- />
22
- <label className="custom-file-label">
23
- {!value && <span>Choose A File...</span>}
24
- {value && (
25
- <span>
26
- {value.name} - size: {humanFileSize(value.size)}
27
- </span>
28
- )}
29
- </label>
30
- </div>
31
- );
32
- function handleChange(e) {
33
- if (e == null || !e.target || !e.target.files.length) {
34
- onChange(null);
35
- } else {
36
- const target = e.target.files[0];
37
- onChange(target);
38
- }
39
- }
40
- }
41
-
42
- function humanFileSize(size) {
43
- const i = Math.floor(Math.log(size) / Math.log(1024));
44
- return (
45
- (size / Math.pow(1024, i)).toFixed(2) * 1 +
46
- ' ' +
47
- ['B', 'KB', 'MB', 'GB', 'TB'][i]
48
- );
49
- }
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import DefaultFileList from './DefaultFileList';
3
+
4
+ export default function FileInput({
5
+ className,
6
+ onChange,
7
+ value,
8
+ multiple = false,
9
+ ...props
10
+ }) {
11
+ const inputRef = useRef();
12
+ const [acceptedFiles, setAcceptedFiles] = useState([]);
13
+ useEffect(() => {
14
+ if (!value && inputRef.current) {
15
+ inputRef.current.value = null;
16
+ }
17
+ }, [value]);
18
+ return (
19
+ <>
20
+ <div
21
+ className={`custom-file file-input ${className || ''} ${
22
+ props.disabled ? 'disabled' : ''
23
+ }`}>
24
+ <div>
25
+ <input
26
+ ref={inputRef}
27
+ type="file"
28
+ className="custom-file-input"
29
+ onChange={handleChange}
30
+ multiple={multiple}
31
+ {...props}
32
+ />
33
+ </div>
34
+ <label className="custom-file-label">
35
+ <div>
36
+ {!value && <span>Choose A File...</span>}
37
+ {value &&
38
+ (!multiple ? (
39
+ <span>
40
+ {value.name} - size: {humanFileSize(value.size)}
41
+ </span>
42
+ ) : value.length === 1 ? (
43
+ <span>
44
+ {value[0].name} - size: {humanFileSize(value[0].size)}
45
+ </span>
46
+ ) : (
47
+ <span>Multiple files selected.</span>
48
+ ))}
49
+ </div>
50
+ </label>
51
+ </div>
52
+ <DefaultFileList files={acceptedFiles} />
53
+ </>
54
+ );
55
+ function handleChange(e) {
56
+ if (e == null || !e.target || !e.target.files.length) {
57
+ onChange(null);
58
+ } else {
59
+ const files = [...e.target.files];
60
+ setAcceptedFiles(files);
61
+ if (!multiple) {
62
+ onChange(files[0]);
63
+ } else {
64
+ onChange(files);
65
+ }
66
+ }
67
+ }
68
+ }
69
+
70
+ function humanFileSize(size) {
71
+ const i = Math.floor(Math.log(size) / Math.log(1024));
72
+ return (
73
+ (size / Math.pow(1024, i)).toFixed(2) * 1 +
74
+ ' ' +
75
+ ['B', 'KB', 'MB', 'GB', 'TB'][i]
76
+ );
77
+ }
@@ -1,15 +1,24 @@
1
- import React from 'react';
2
- import { render, screen } from '@testing-library/react';
3
-
4
- import FileInput from './FileInput';
5
-
6
- describe('FileInput', () => {
7
- it('Renders without error', () => {
8
- render(<FileInput name="myFile" />);
9
- });
10
-
11
- it('has matching snapshot', () => {
12
- const renderResult = render(<FileInput name="myFile" />);
13
- expect(renderResult.asFragment()).toMatchSnapshot();
14
- });
15
- });
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+
4
+ import FileInput from './FileInput';
5
+
6
+ describe('FileInput', () => {
7
+ it('Renders without error', () => {
8
+ render(<FileInput name="myFile" />);
9
+ });
10
+
11
+ it('Renders multiple files without error', () => {
12
+ render(<FileInput name="myFile" multiple />);
13
+ });
14
+
15
+ it('has matching snapshot', () => {
16
+ const renderResult = render(<FileInput name="myFile" />);
17
+ expect(renderResult.asFragment()).toMatchSnapshot();
18
+ });
19
+
20
+ it('multiple files has matching snapshot', () => {
21
+ const renderResult = render(<FileInput name="myFile" multiple />);
22
+ expect(renderResult.asFragment()).toMatchSnapshot();
23
+ });
24
+ });
@@ -0,0 +1,57 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`DropZoneFileInput Multiple file input has matching snapshot 1`] = `
4
+ <DocumentFragment>
5
+ <section
6
+ class="react-dropzone"
7
+ >
8
+ <div
9
+ tabindex="0"
10
+ >
11
+ <input
12
+ autocomplete="off"
13
+ multiple=""
14
+ name="myFile"
15
+ style="display: none;"
16
+ tabindex="-1"
17
+ type="file"
18
+ />
19
+ Drag and drop some file(s) here, or click to select file(s)
20
+ </div>
21
+ <aside>
22
+ <ul
23
+ class="list-group"
24
+ />
25
+ </aside>
26
+ </section>
27
+ </DocumentFragment>
28
+ `;
29
+
30
+ exports[`DropZoneFileInput has matching snapshot 1`] = `
31
+ <DocumentFragment>
32
+ <section
33
+ class="react-dropzone"
34
+ >
35
+ <div
36
+ tabindex="0"
37
+ >
38
+ <input
39
+ autocomplete="off"
40
+ name="myFile"
41
+ style="display: none;"
42
+ tabindex="-1"
43
+ type="file"
44
+ />
45
+ Drag and drop some file(s) here, or click to select file(s)
46
+ <b>
47
+ Only one file accepted.
48
+ </b>
49
+ </div>
50
+ <aside>
51
+ <ul
52
+ class="list-group"
53
+ />
54
+ </aside>
55
+ </section>
56
+ </DocumentFragment>
57
+ `;
@@ -1,22 +1,58 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`FileInput has matching snapshot 1`] = `
4
- <DocumentFragment>
5
- <div
6
- class="custom-file file-input "
7
- >
8
- <input
9
- class="custom-file-input"
10
- name="myFile"
11
- type="file"
12
- />
13
- <label
14
- class="custom-file-label"
15
- >
16
- <span>
17
- Choose A File...
18
- </span>
19
- </label>
20
- </div>
21
- </DocumentFragment>
22
- `;
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`FileInput has matching snapshot 1`] = `
4
+ <DocumentFragment>
5
+ <div
6
+ class="custom-file file-input "
7
+ >
8
+ <div>
9
+ <input
10
+ class="custom-file-input"
11
+ name="myFile"
12
+ type="file"
13
+ />
14
+ </div>
15
+ <label
16
+ class="custom-file-label"
17
+ >
18
+ <div>
19
+ <span>
20
+ Choose A File...
21
+ </span>
22
+ </div>
23
+ </label>
24
+ </div>
25
+ <ul
26
+ class="list-group"
27
+ />
28
+ </DocumentFragment>
29
+ `;
30
+
31
+ exports[`FileInput multiple files has matching snapshot 1`] = `
32
+ <DocumentFragment>
33
+ <div
34
+ class="custom-file file-input "
35
+ >
36
+ <div>
37
+ <input
38
+ class="custom-file-input"
39
+ multiple=""
40
+ name="myFile"
41
+ type="file"
42
+ />
43
+ </div>
44
+ <label
45
+ class="custom-file-label"
46
+ >
47
+ <div>
48
+ <span>
49
+ Choose A File...
50
+ </span>
51
+ </div>
52
+ </label>
53
+ </div>
54
+ <ul
55
+ class="list-group"
56
+ />
57
+ </DocumentFragment>
58
+ `;
@@ -1,17 +1,58 @@
1
- @import '../variables';
2
-
3
- .file-input {
4
- &.is-invalid {
5
- .custom-file-label {
6
- border: 1px solid $red;
7
- }
8
- }
9
- }
10
-
11
- .custom-file-input:disabled ~ .custom-file-label {
12
- background-color: $input-disabled-background-color;
13
- border-color: $input-disabled-border-color;
14
- &:after {
15
- display: none !important;
16
- }
17
- }
1
+ @import '../variables';
2
+
3
+ .file-input {
4
+ &.is-invalid {
5
+ .custom-file-label {
6
+ border: 1px solid $red;
7
+ }
8
+ }
9
+ }
10
+
11
+ .custom-file-input:disabled ~ .custom-file-label {
12
+ background-color: $input-disabled-background-color;
13
+ border-color: $input-disabled-border-color;
14
+ &:after {
15
+ display: none !important;
16
+ }
17
+ }
18
+
19
+ section.react-dropzone {
20
+ & > div {
21
+ display: flex;
22
+ flex: 1;
23
+ flex-direction: column;
24
+ align-items: center;
25
+ border: 1px dashed #d8dbe0;
26
+ border-radius: 0.25rem;
27
+ background-color: white;
28
+ color: #8a93a2;
29
+ outline: none;
30
+ margin: 0;
31
+ padding: 20px;
32
+ transition: 'border .24s ease-in-out';
33
+ cursor: pointer;
34
+ &:focus,
35
+ &:focus-within {
36
+ border-color: #958bef;
37
+ box-shadow: 0 0 0 0.2rem rgba(50, 31, 219, 0.25);
38
+ }
39
+ }
40
+ &.active > div,
41
+ &.accept > div {
42
+ border-color: #958bef;
43
+ box-shadow: 0 0 0 0.2rem rgba(50, 31, 219, 0.25);
44
+ }
45
+ &.reject > div {
46
+ color: rgb(229, 83, 83);
47
+ border-color: rgb(229, 83, 83);
48
+ box-shadow: rgba(229, 83, 83, 0.25) 0px 0px 0px 3.2px;
49
+ }
50
+ &.disabled > div {
51
+ background-color: $input-disabled-background-color;
52
+ border-color: $input-disabled-border-color;
53
+ cursor: no-drop;
54
+ }
55
+ & > aside > .list-group:not(:empty) {
56
+ margin-top: 1rem;
57
+ }
58
+ }
@@ -1,3 +1,4 @@
1
- import FileInput from './FileInput';
2
-
3
- export default FileInput;
1
+ import FileInput from './FileInput';
2
+ import DropzoneFileInput from './DropzoneFileInput';
3
+
4
+ export { FileInput, DropzoneFileInput };
@@ -1,48 +1,48 @@
1
- import { useEffect } from 'react';
2
- import { useFormikContext } from 'formik';
3
- import smoothscroll from 'smoothscroll-polyfill';
4
-
5
- export default function FocusError(props) {
6
- const { errors, isSubmitting, isValidating } = useFormikContext();
7
- smoothscroll.polyfill();
8
- useEffect(() => {
9
- if (!isSubmitting || isValidating) {
10
- return;
11
- }
12
- //This block handles any front-end input validation errors
13
- //e.g. required fields, max characters, etc
14
- const keys = Object.keys(errors);
15
- if (keys.length > 0) {
16
- return scrollToErrorElement(keys);
17
- }
18
- //This block handles any input-specific server-side errors
19
- //e.g. improper email formatting, invalid phone number, etc.
20
- if (
21
- props.serverErrors.errors &&
22
- Object.values(props.serverErrors.errors).some((x) => !!x)
23
- ) {
24
- const names = Object.keys(props.serverErrors.errors);
25
- return scrollToErrorElement(names);
26
- }
27
- }, [errors, isSubmitting, isValidating, props]);
28
- return null;
29
- }
30
-
31
- const scrollToErrorElement = (keys) => {
32
- let firstErrorElement = document.getElementById(
33
- `${keys[0].toLowerCase()}-error-scroll-target`
34
- );
35
- if (firstErrorElement == null || firstErrorElement.parentNode == null) {
36
- return;
37
- }
38
- firstErrorElement = firstErrorElement.parentNode;
39
- const headerOffset = -110;
40
- const y =
41
- firstErrorElement.getBoundingClientRect().top +
42
- window.pageYOffset +
43
- headerOffset;
44
- window.scrollTo({ top: y, behavior: 'smooth' });
45
- setTimeout(() => {
46
- firstErrorElement.focus();
47
- }, 500);
48
- };
1
+ import { useEffect } from 'react';
2
+ import { useFormikContext } from 'formik';
3
+ import smoothscroll from 'smoothscroll-polyfill';
4
+
5
+ export default function FocusError(props) {
6
+ const { errors, isSubmitting, isValidating } = useFormikContext();
7
+ smoothscroll.polyfill();
8
+ useEffect(() => {
9
+ if (!isSubmitting || isValidating) {
10
+ return;
11
+ }
12
+ //This block handles any front-end input validation errors
13
+ //e.g. required fields, max characters, etc
14
+ const keys = Object.keys(errors);
15
+ if (keys.length > 0) {
16
+ return scrollToErrorElement(keys);
17
+ }
18
+ //This block handles any input-specific server-side errors
19
+ //e.g. improper email formatting, invalid phone number, etc.
20
+ if (
21
+ props.serverErrors.errors &&
22
+ Object.values(props.serverErrors.errors).some((x) => !!x)
23
+ ) {
24
+ const names = Object.keys(props.serverErrors.errors);
25
+ return scrollToErrorElement(names);
26
+ }
27
+ }, [errors, isSubmitting, isValidating, props]);
28
+ return null;
29
+ }
30
+
31
+ const scrollToErrorElement = (keys) => {
32
+ let firstErrorElement = document.getElementById(
33
+ `${keys[0].toLowerCase()}-error-scroll-target`
34
+ );
35
+ if (firstErrorElement == null || firstErrorElement.parentNode == null) {
36
+ return;
37
+ }
38
+ firstErrorElement = firstErrorElement.parentNode;
39
+ const headerOffset = -110;
40
+ const y =
41
+ firstErrorElement.getBoundingClientRect().top +
42
+ window.pageYOffset +
43
+ headerOffset;
44
+ window.scrollTo({ top: y, behavior: 'smooth' });
45
+ setTimeout(() => {
46
+ firstErrorElement.focus();
47
+ }, 500);
48
+ };