envoc-form 4.0.1-1 → 4.0.1-11

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 (153) hide show
  1. package/README.md +2 -2
  2. package/es/AddressInput/AddressInput.d.ts +5 -5
  3. package/es/ConfirmBaseForm/ConfirmBaseForm.d.ts +3 -1
  4. package/es/ConfirmBaseForm/ConfirmBaseForm.js +3 -2
  5. package/es/ConfirmDeleteForm/ConfirmDeleteForm.js +2 -1
  6. package/es/DatePicker/DatePickerGroup.d.ts +3 -2
  7. package/es/DatePicker/DatePickerGroup.js +27 -5
  8. package/es/DatePicker/StringDateOnlyPickerGroup.d.ts +5 -0
  9. package/es/DatePicker/{DateOnlyDatePickerGroup.js → StringDateOnlyPickerGroup.js} +7 -6
  10. package/es/DatePicker/StringDatePickerGroup.d.ts +1 -1
  11. package/es/Field/Field.d.ts +3 -3
  12. package/es/Field/Field.js +9 -4
  13. package/es/FieldArray/FieldArray.d.ts +2 -2
  14. package/es/FieldArray/FieldArray.js +10 -7
  15. package/es/File/FileGroup.d.ts +1 -1
  16. package/es/File/FileGroup.js +5 -3
  17. package/es/File/FileList.d.ts +2 -2
  18. package/es/File/FileList.js +2 -1
  19. package/es/Form/Form.d.ts +8 -3
  20. package/es/Form/Form.js +32 -3
  21. package/es/Form/FormBasedPreventNavigation.js +31 -14
  22. package/es/FormActions.js +5 -1
  23. package/es/FormDefaults.d.ts +3 -0
  24. package/es/FormDefaults.js +1 -0
  25. package/es/Group.d.ts +2 -1
  26. package/es/Group.js +8 -5
  27. package/es/Input/IconInputGroup.d.ts +1 -1
  28. package/es/Input/IconInputGroup.js +3 -1
  29. package/es/Input/InputGroup.d.ts +3 -3
  30. package/es/Input/InputGroup.js +3 -2
  31. package/es/Input/MoneyInputGroup.d.ts +1 -1
  32. package/es/Input/MoneyInputGroup.js +2 -1
  33. package/es/Input/NumberInputGroup.d.ts +1 -1
  34. package/es/Input/NumberInputGroup.js +2 -1
  35. package/es/Input/StringInputGroup.d.ts +1 -1
  36. package/es/Input/StringInputGroup.js +3 -1
  37. package/es/Normalization/normalizers.d.ts +2 -2
  38. package/es/Select/BooleanSelectGroup.d.ts +1 -1
  39. package/es/Select/NumberSelectGroup.d.ts +2 -2
  40. package/es/Select/SelectGroup.d.ts +2 -2
  41. package/es/Select/SelectGroup.js +8 -3
  42. package/es/StandardFormActions.js +2 -1
  43. package/es/SubmitFormButton.d.ts +1 -1
  44. package/es/SubmitFormButton.js +4 -2
  45. package/es/TextArea/TextAreaGroup.d.ts +1 -1
  46. package/es/TextArea/TextAreaGroup.js +4 -2
  47. package/es/Validation/validators.d.ts +8 -8
  48. package/es/Validation/validators.js +3 -0
  49. package/es/__Tests__/FormTestBase.d.ts +3 -3
  50. package/es/__Tests__/FormTestBase.js +1 -1
  51. package/es/index.d.ts +8 -6
  52. package/es/index.js +4 -1
  53. package/lib/AddressInput/AddressInput.d.ts +5 -5
  54. package/lib/ConfirmBaseForm/ConfirmBaseForm.d.ts +3 -1
  55. package/lib/ConfirmBaseForm/ConfirmBaseForm.js +3 -2
  56. package/lib/ConfirmDeleteForm/ConfirmDeleteForm.js +2 -1
  57. package/lib/DatePicker/DatePickerGroup.d.ts +3 -2
  58. package/lib/DatePicker/DatePickerGroup.js +28 -4
  59. package/lib/DatePicker/StringDateOnlyPickerGroup.d.ts +5 -0
  60. package/lib/DatePicker/{DateOnlyDatePickerGroup.js → StringDateOnlyPickerGroup.js} +8 -7
  61. package/lib/DatePicker/StringDatePickerGroup.d.ts +1 -1
  62. package/lib/Field/Field.d.ts +3 -3
  63. package/lib/Field/Field.js +9 -4
  64. package/lib/FieldArray/FieldArray.d.ts +2 -2
  65. package/lib/FieldArray/FieldArray.js +10 -7
  66. package/lib/File/FileGroup.d.ts +1 -1
  67. package/lib/File/FileGroup.js +5 -3
  68. package/lib/File/FileList.d.ts +2 -2
  69. package/lib/File/FileList.js +2 -1
  70. package/lib/Form/Form.d.ts +8 -3
  71. package/lib/Form/Form.js +32 -3
  72. package/lib/Form/FormBasedPreventNavigation.js +31 -14
  73. package/lib/FormActions.js +5 -1
  74. package/lib/FormDefaults.d.ts +3 -0
  75. package/lib/FormDefaults.js +4 -0
  76. package/lib/Group.d.ts +2 -1
  77. package/lib/Group.js +8 -5
  78. package/lib/Input/IconInputGroup.d.ts +1 -1
  79. package/lib/Input/IconInputGroup.js +3 -1
  80. package/lib/Input/InputGroup.d.ts +3 -3
  81. package/lib/Input/InputGroup.js +3 -2
  82. package/lib/Input/MoneyInputGroup.d.ts +1 -1
  83. package/lib/Input/MoneyInputGroup.js +2 -1
  84. package/lib/Input/NumberInputGroup.d.ts +1 -1
  85. package/lib/Input/NumberInputGroup.js +2 -1
  86. package/lib/Input/StringInputGroup.d.ts +1 -1
  87. package/lib/Input/StringInputGroup.js +3 -1
  88. package/lib/Normalization/normalizers.d.ts +2 -2
  89. package/lib/Select/BooleanSelectGroup.d.ts +1 -1
  90. package/lib/Select/NumberSelectGroup.d.ts +2 -2
  91. package/lib/Select/SelectGroup.d.ts +2 -2
  92. package/lib/Select/SelectGroup.js +8 -3
  93. package/lib/StandardFormActions.js +2 -1
  94. package/lib/SubmitFormButton.d.ts +1 -1
  95. package/lib/SubmitFormButton.js +4 -2
  96. package/lib/TextArea/TextAreaGroup.d.ts +1 -1
  97. package/lib/TextArea/TextAreaGroup.js +4 -2
  98. package/lib/Validation/validators.d.ts +8 -8
  99. package/lib/Validation/validators.js +3 -0
  100. package/lib/__Tests__/FormTestBase.d.ts +3 -3
  101. package/lib/__Tests__/FormTestBase.js +2 -2
  102. package/lib/index.d.ts +8 -6
  103. package/lib/index.js +8 -3
  104. package/package.json +4 -2
  105. package/src/AddressInput/AddressInput.tsx +5 -5
  106. package/src/AddressInput/__snapshots__/AddressInput.test.tsx.snap +15 -10
  107. package/src/ConfirmBaseForm/ConfirmBaseForm.tsx +13 -3
  108. package/src/ConfirmBaseForm/__snapshots__/ConfirmBaseForm.test.tsx.snap +3 -3
  109. package/src/ConfirmDeleteForm/ConfirmDeleteForm.tsx +8 -1
  110. package/src/ConfirmDeleteForm/__snapshots__/ConfirmDeleteForm.test.tsx.snap +2 -2
  111. package/src/DatePicker/DatePicker.test.tsx +3 -3
  112. package/src/DatePicker/DatePickerGroup.tsx +45 -8
  113. package/src/DatePicker/StringDateOnlyPickerGroup.tsx +23 -0
  114. package/src/DatePicker/StringDatePickerGroup.tsx +1 -1
  115. package/src/DatePicker/__snapshots__/DatePicker.test.tsx.snap +3 -2
  116. package/src/Field/Field.tsx +22 -7
  117. package/src/FieldArray/FieldArray.tsx +25 -13
  118. package/src/File/FileGroup.tsx +15 -3
  119. package/src/File/FileList.tsx +5 -3
  120. package/src/File/__snapshots__/FileGroup.test.tsx.snap +5 -3
  121. package/src/Form/Form.tsx +56 -4
  122. package/src/Form/FormBasedPreventNavigation.tsx +34 -18
  123. package/src/Form/__snapshots__/Form.test.tsx.snap +1 -0
  124. package/src/FormActions.tsx +8 -2
  125. package/src/FormDefaults.ts +1 -0
  126. package/src/Group.tsx +21 -6
  127. package/src/Input/IconInputGroup.tsx +7 -4
  128. package/src/Input/InputGroup.tsx +18 -5
  129. package/src/Input/MoneyInputGroup.tsx +6 -2
  130. package/src/Input/NumberInputGroup.tsx +6 -2
  131. package/src/Input/StringInputGroup.tsx +7 -3
  132. package/src/Input/__Tests__/__snapshots__/IconInputGroup.test.tsx.snap +4 -2
  133. package/src/Input/__Tests__/__snapshots__/MoneyInputGroup.test.tsx.snap +4 -2
  134. package/src/Input/__Tests__/__snapshots__/NumberInputGroup.test.tsx.snap +4 -2
  135. package/src/Input/__Tests__/__snapshots__/StringInputGroup.test.tsx.snap +4 -2
  136. package/src/Normalization/normalizers.ts +2 -2
  137. package/src/Select/BooleanSelectGroup.tsx +1 -1
  138. package/src/Select/NumberSelectGroup.tsx +2 -2
  139. package/src/Select/SelectGroup.tsx +16 -4
  140. package/src/Select/__tests__/__snapshots__/BooleanSelectGroup.test.tsx.snap +3 -2
  141. package/src/Select/__tests__/__snapshots__/NumberSelectGroup.test.tsx.snap +6 -4
  142. package/src/Select/__tests__/__snapshots__/StringSelectGroup.test.tsx.snap +6 -4
  143. package/src/StandardFormActions.tsx +4 -1
  144. package/src/SubmitFormButton.tsx +9 -2
  145. package/src/TextArea/TextAreaGroup.tsx +13 -4
  146. package/src/Validation/validators.ts +16 -12
  147. package/src/__Tests__/FormTestBase.tsx +4 -4
  148. package/src/__Tests__/__snapshots__/StandardFormActions.test.tsx.snap +4 -2
  149. package/src/__Tests__/__snapshots__/SubmitFormButton.test.tsx.snap +3 -1
  150. package/src/index.ts +11 -10
  151. package/es/DatePicker/DateOnlyDatePickerGroup.d.ts +0 -10
  152. package/lib/DatePicker/DateOnlyDatePickerGroup.d.ts +0 -10
  153. package/src/DatePicker/DateOnlyDatePickerGroup.tsx +0 -24
@@ -4,6 +4,7 @@ import { InjectedFieldProps } from './InjectedFieldProps';
4
4
  import useStandardFormInput from './useStandardField';
5
5
  import { NormalizationFunction } from '../Normalization/NormalizationFunction';
6
6
  import { ValidationFunction } from '../Validation/ValidationFunction';
7
+ import { required as requiredValidator } from '../Validation/validators';
7
8
 
8
9
  // we attempted to support generic components but failed
9
10
  // so, we assume the actual TRenderComponent has no generic arguments
@@ -13,7 +14,7 @@ export type RenderComponent<
13
14
  TValue,
14
15
  TRenderComponent extends ElementType
15
16
  > = Partial<ComponentProps<TRenderComponent>> extends Partial<
16
- InjectedFieldProps<TValue>
17
+ InjectedFieldProps<TValue | undefined | null>
17
18
  >
18
19
  ? TRenderComponent
19
20
  : never;
@@ -22,7 +23,7 @@ export type RenderComponentProps<
22
23
  TValue,
23
24
  TRenderComponent extends ElementType
24
25
  > = Partial<ComponentProps<TRenderComponent>> extends Partial<
25
- InjectedFieldProps<TValue>
26
+ InjectedFieldProps<TValue | undefined | null>
26
27
  >
27
28
  ? ComponentProps<TRenderComponent>
28
29
  : never;
@@ -60,9 +61,9 @@ function Field<
60
61
  name,
61
62
  Component,
62
63
  id,
63
- normalize,
64
- validate,
65
64
  disabled,
65
+ validate,
66
+ normalize,
66
67
  ...rest
67
68
  }: FieldProps<TForm, TProp, TRenderComponent>,
68
69
  ref: LegacyRef<any>
@@ -70,11 +71,17 @@ function Field<
70
71
  const [input, meta] = useStandardFormInput<TForm[TProp]>({
71
72
  name: String(name),
72
73
  id: id,
73
- normalize: normalize,
74
- validate: validate,
75
74
  disabled: disabled,
75
+ validate: validate,
76
+ normalize: normalize,
76
77
  });
77
78
 
79
+ const isRequired =
80
+ rest?.required !== undefined
81
+ ? rest.required
82
+ : validate === requiredValidator ||
83
+ (Array.isArray(validate) && validate.includes(requiredValidator));
84
+
78
85
  // a bit of a hack so JSX is happy with us
79
86
  const Wrapped = Component as React.ComponentType<
80
87
  InjectedFieldProps<TForm[TProp]>
@@ -82,7 +89,15 @@ function Field<
82
89
 
83
90
  return (
84
91
  <FieldNameContext.Provider value={input.name}>
85
- <Wrapped {...rest} ref={ref} id={id} input={input} meta={meta} />
92
+ <Wrapped
93
+ {...rest}
94
+ ref={ref}
95
+ id={input.id}
96
+ input={input}
97
+ meta={meta}
98
+ required={isRequired}
99
+ disabled={disabled}
100
+ />
86
101
  </FieldNameContext.Provider>
87
102
  );
88
103
  }
@@ -3,12 +3,13 @@ import classNames from 'classnames';
3
3
  import Field, { FieldProps } from '../Field/Field';
4
4
  import { FieldNameContext } from '../Field/FieldNameContext';
5
5
  import useStandardFormInput from '../Field/useStandardField';
6
+ import { FormDefaults } from '../FormDefaults';
6
7
  import { ValidationFunction } from '../Validation/ValidationFunction';
7
8
 
8
9
  export type FieldArrayProps<
9
10
  TForm extends object,
10
11
  TProp extends keyof TForm
11
- > = TForm[TProp] extends Array<any> | undefined
12
+ > = TForm[TProp] extends Array<any> | undefined | null
12
13
  ? {
13
14
  name: TProp;
14
15
  label?: string;
@@ -22,8 +23,8 @@ export type FieldArrayProps<
22
23
  }
23
24
  : never;
24
25
 
25
- export type ArrayFormBuilderProp<TValue extends Array<any> | undefined> =
26
- TValue extends Array<infer TForm> | undefined
26
+ export type ArrayFormBuilderProp<TValue extends Array<any> | undefined | null> =
27
+ TValue extends Array<infer TForm> | undefined | null
27
28
  ? TForm extends object
28
29
  ? {
29
30
  Field: <
@@ -51,7 +52,7 @@ export default function FieldArray<
51
52
  children,
52
53
  ...rest
53
54
  }: FieldArrayProps<TForm, TProp>) {
54
- const [input, meta] = useStandardFormInput<TForm[TProp]>({
55
+ const [input] = useStandardFormInput<TForm[TProp]>({
55
56
  name: String(name),
56
57
  validate: validate,
57
58
  disabled: disabled,
@@ -64,18 +65,23 @@ export default function FieldArray<
64
65
  : [];
65
66
 
66
67
  return (
67
- <div className="field-array">
68
- <div className="field-array-header">
69
- <div className="field-array-title">{label}</div>
68
+ <div className={FormDefaults.cssClassPrefix + 'field-array'}>
69
+ <div className={FormDefaults.cssClassPrefix + 'field-array-header'}>
70
+ <div className={FormDefaults.cssClassPrefix + 'field-array-title'}>
71
+ {label}
72
+ </div>
70
73
  <button
71
- className={classNames('add-array-item-button', { disabled })}
74
+ className={classNames(
75
+ FormDefaults.cssClassPrefix + 'add-array-item-button',
76
+ { [FormDefaults.cssClassPrefix + 'disabled']: disabled }
77
+ )}
72
78
  title="Add Item"
73
79
  type="button"
74
80
  onClick={addItem}>
75
81
  +
76
82
  </button>
77
83
  </div>
78
- <div className="field-array-body">
84
+ <div className={FormDefaults.cssClassPrefix + 'field-array-body'}>
79
85
  {values.map((value, index) => {
80
86
  const itemName = `${input.name}[${index}]`;
81
87
  return (
@@ -85,9 +91,12 @@ export default function FieldArray<
85
91
  (value && value['id']) ||
86
92
  itemName
87
93
  }
88
- className={classNames('field-array-item', {
89
- removed: value.isDeleted,
90
- })}
94
+ className={classNames(
95
+ FormDefaults.cssClassPrefix + 'field-array-item',
96
+ {
97
+ [FormDefaults.cssClassPrefix + 'removed']: value.isDeleted,
98
+ }
99
+ )}
91
100
  role="listitem">
92
101
  <FieldNameContext.Provider value={itemName}>
93
102
  {children({
@@ -96,7 +105,10 @@ export default function FieldArray<
96
105
  } as any)}
97
106
  </FieldNameContext.Provider>
98
107
  <button
99
- className={classNames('remove-array-item-button', { disabled })}
108
+ className={classNames(
109
+ FormDefaults.cssClassPrefix + 'remove-array-item-button',
110
+ { [FormDefaults.cssClassPrefix + 'disabled']: disabled }
111
+ )}
100
112
  type="button"
101
113
  title="Remove Item"
102
114
  onClick={() => removeItem(value)}>
@@ -2,11 +2,12 @@ import React, { ComponentType, LegacyRef } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import FileList from './FileList';
4
4
  import { InjectedFieldProps } from '../Field/InjectedFieldProps';
5
+ import { FormDefaults } from '../FormDefaults';
5
6
  import Group, { GroupProps } from '../Group';
6
7
 
7
8
  export interface FileGroupProps
8
9
  // note: file props are of type "any" with the current type generation
9
- extends InjectedFieldProps<any | undefined>,
10
+ extends InjectedFieldProps<any | undefined | null>,
10
11
  Omit<GroupProps, keyof InjectedFieldProps<any> | 'children'>,
11
12
  Omit<
12
13
  React.HTMLProps<HTMLInputElement>,
@@ -22,6 +23,8 @@ function FileGroup(
22
23
  label,
23
24
  helpText,
24
25
  className,
26
+ required,
27
+ disabled,
25
28
  multiple,
26
29
  ...rest
27
30
  }: FileGroupProps,
@@ -33,7 +36,13 @@ function FileGroup(
33
36
  meta={meta}
34
37
  label={label}
35
38
  helpText={helpText}
36
- className={classNames(className, { multiple }, 'file-group')}>
39
+ className={classNames(
40
+ className,
41
+ { [FormDefaults.cssClassPrefix + 'multiple']: multiple },
42
+ FormDefaults.cssClassPrefix + 'file-group'
43
+ )}
44
+ required={required}
45
+ disabled={disabled}>
37
46
  <input
38
47
  {...input}
39
48
  {...rest}
@@ -56,7 +65,10 @@ function FileGroup(
56
65
  value={undefined}
57
66
  ref={ref}
58
67
  type="file"
59
- className={classNames(className, 'file-group')}
68
+ className={classNames(
69
+ className,
70
+ FormDefaults.cssClassPrefix + 'file-group'
71
+ )}
60
72
  />
61
73
  {/* Note: because input.value is any - due to how files are currently handled - type safeness isn't great here */}
62
74
  <FileList files={input.value} />
@@ -1,10 +1,12 @@
1
+ import { FormDefaults } from '../FormDefaults';
2
+
1
3
  export interface FileListProps {
2
- files?: File | File[] | undefined;
3
- rejectedFiles?: File | File[] | undefined;
4
+ files?: File | File[] | undefined | null;
5
+ rejectedFiles?: File | File[] | undefined | null;
4
6
  }
5
7
  export default function FileList({ files, rejectedFiles }: FileListProps) {
6
8
  return (
7
- <div className="file-list">
9
+ <div className={FormDefaults.cssClassPrefix + 'file-list'}>
8
10
  {!files ? null : Array.isArray(files) ? (
9
11
  files.map((x, i) => <File file={x} key={i} />)
10
12
  ) : (
@@ -4,9 +4,10 @@ exports[`FileGroup has matching snapshot 1`] = `
4
4
  <DocumentFragment>
5
5
  <form
6
6
  action="#"
7
+ class="envoc-form-form"
7
8
  >
8
9
  <div
9
- class="file-group group"
10
+ class="envoc-form-file-group envoc-form-group"
10
11
  >
11
12
  <div
12
13
  id="profileimage-error-scroll-target"
@@ -18,13 +19,14 @@ exports[`FileGroup has matching snapshot 1`] = `
18
19
  Profile Image
19
20
  </label>
20
21
  <input
21
- class="file-group"
22
+ class="envoc-form-file-group"
23
+ id="profileImage"
22
24
  name="profileImage"
23
25
  type="file"
24
26
  value=""
25
27
  />
26
28
  <div
27
- class="file-list"
29
+ class="envoc-form-file-list"
28
30
  />
29
31
  </div>
30
32
  </form>
package/src/Form/Form.tsx CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  useMemo,
6
6
  useState,
7
7
  } from 'react';
8
+ import classNames from 'classnames';
8
9
  import {
9
10
  Form as FormikFormWrapper,
10
11
  Formik,
@@ -20,6 +21,7 @@ import {
20
21
  } from './ServerErrorContext';
21
22
  import Field, { FieldProps } from '../Field/Field';
22
23
  import FieldArray, { FieldArrayProps } from '../FieldArray/FieldArray';
24
+ import { FormDefaults } from '../FormDefaults';
23
25
  import objectContainsNonSerializableProperty from '../utils/objectContainsNonSerializableProperty';
24
26
  import objectToFormData from '../utils/objectToFormData';
25
27
  import { ValidatedApiResult } from '../Validation/ValidatedApiResult';
@@ -38,10 +40,14 @@ export type FormBuilderProp<TForm extends object> = {
38
40
  ) => JSX.Element;
39
41
  };
40
42
 
41
- export interface FormProps<TForm extends object> {
43
+ export interface FullFormProps<TForm extends object> {
42
44
  children: (formBuilder: FormBuilderProp<TForm>) => JSX.Element;
43
45
  onSubmit: (
44
- formValues: FormData | TForm,
46
+ formValues: TForm,
47
+ formikBag: FormikHelpers<TForm>
48
+ ) => Promise<ValidatedApiResult>;
49
+ onFormDataSubmit: (
50
+ formValues: FormData,
45
51
  formikBag: FormikHelpers<TForm>
46
52
  ) => Promise<ValidatedApiResult>;
47
53
  className?: string;
@@ -50,12 +56,26 @@ export interface FormProps<TForm extends object> {
50
56
  initialValues?: TForm;
51
57
  }
52
58
 
59
+ type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<
60
+ T,
61
+ Exclude<keyof T, Keys>
62
+ > &
63
+ {
64
+ [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>;
65
+ }[Keys];
66
+
67
+ export type FormProps<TForm extends object> = RequireAtLeastOne<
68
+ FullFormProps<TForm>,
69
+ 'onSubmit' | 'onFormDataSubmit'
70
+ >;
71
+
53
72
  export default function Form<TForm extends object>({
54
73
  children,
55
74
  className,
56
75
  style,
57
76
  ignoreLostChanges,
58
77
  onSubmit,
78
+ onFormDataSubmit,
59
79
  initialValues,
60
80
  ...props
61
81
  }: FormProps<TForm>) {
@@ -92,7 +112,12 @@ export default function Form<TForm extends object>({
92
112
  onSubmit={handleSubmit}
93
113
  {...props}>
94
114
  <ServerErrorContext.Provider value={serverErrorContextValue}>
95
- <FormikFormWrapper className={className} style={style}>
115
+ <FormikFormWrapper
116
+ className={classNames(
117
+ className,
118
+ FormDefaults.cssClassPrefix + 'form'
119
+ )}
120
+ style={style}>
96
121
  <FocusError serverErrors={serverErrorContextValue} />
97
122
  <FormBasedPreventNavigation ignoreLostChanges={ignoreLostChanges} />
98
123
  {children({
@@ -107,6 +132,7 @@ export default function Form<TForm extends object>({
107
132
 
108
133
  function handleSubmit(values: TForm, formikBag: FormikHelpers<TForm>) {
109
134
  let formData: FormData | undefined = undefined;
135
+ let submitFunc: () => Promise<ValidatedApiResult>;
110
136
  if (objectContainsNonSerializableProperty(values)) {
111
137
  formData = objectToFormData(values, {
112
138
  indices: true,
@@ -114,9 +140,35 @@ export default function Form<TForm extends object>({
114
140
  allowEmptyArrays: true,
115
141
  noFileListBrackets: true,
116
142
  });
143
+ if (onFormDataSubmit === undefined) {
144
+ throw new Error(
145
+ 'No onFormDataSubmit supplied for non-serializable properties.'
146
+ );
147
+ }
148
+ submitFunc = () =>
149
+ onFormDataSubmit(formData ?? new FormData(), formikBag);
150
+ } else {
151
+ if (onSubmit === undefined) {
152
+ formData = objectToFormData(values, {
153
+ indices: true,
154
+ dotNotation: true,
155
+ allowEmptyArrays: true,
156
+ noFileListBrackets: true,
157
+ });
158
+ if (onFormDataSubmit === undefined) {
159
+ // This error should never occur, as this case is covered by RequireAtLeastOne type safety
160
+ throw new Error(
161
+ 'No onFormDataSubmit supplied for non-serializable properties.'
162
+ );
163
+ }
164
+ submitFunc = () =>
165
+ onFormDataSubmit(formData ?? new FormData(), formikBag);
166
+ } else {
167
+ submitFunc = () => onSubmit(values, formikBag);
168
+ }
117
169
  }
118
170
 
119
- return onSubmit(formData ?? values, formikBag)
171
+ return submitFunc()
120
172
  .then((response) => {
121
173
  return response;
122
174
  })
@@ -8,8 +8,13 @@ import {
8
8
  import { useFormikContext } from 'formik';
9
9
 
10
10
  interface Navigator extends BaseNavigator {
11
- block: History['block'];
11
+ block?: History['block'];
12
12
  }
13
+
14
+ //The Current state of the world (2023-05-10) is that useBlocker exists in react-router, but is only available for data routers, which we currently don't use
15
+ // `block` was available on UNSAFE_NavigationContext previously, but was removed
16
+ // Modifying `push` prevents _most_ but not all navigations with a prompt, long term solution may be to remove FileSystemRoutes and swap to a data router (https://reactrouter.com/en/main/routers/create-browser-router)
17
+
13
18
  // see: https://github.com/remix-run/react-router/issues/8139#issuecomment-1023105785
14
19
  type NavigationContextWithBlock = ContextType<typeof NavigationContext> & {
15
20
  navigator: Navigator;
@@ -34,33 +39,44 @@ export default function FormBasedPreventNavigation({
34
39
  if (!preventNavigate) {
35
40
  return;
36
41
  }
42
+ let unblock = () => {};
43
+ const push = navigator.push;
37
44
 
38
45
  // TODO: https://reactrouter.com/docs/en/v6/upgrading/v5#prompt-is-not-currently-supported
39
46
  // this is a workaround until we get native support for prompt on navigate
40
-
41
- const blocker: Blocker = (tx) => {
42
- if (window.confirm(promptMessage)) {
43
- tx.retry();
44
- }
45
- };
46
- const unblock = navigator.block((tx: Transition) => {
47
- const autoUnblockingTx = {
48
- ...tx,
49
- retry() {
50
- // Automatically unblock the transition so it can play all the way
51
- // through before retrying it. TODO: Figure out how to re-enable
52
- // this block if the transition is cancelled for some reason.
53
- unblock();
47
+ if (navigator.block) {
48
+ const blocker: Blocker = (tx) => {
49
+ if (window.confirm(promptMessage)) {
54
50
  tx.retry();
55
- },
51
+ }
56
52
  };
53
+ unblock = navigator.block((tx: Transition) => {
54
+ const autoUnblockingTx = {
55
+ ...tx,
56
+ retry() {
57
+ // Automatically unblock the transition so it can play all the way
58
+ // through before retrying it. TODO: Figure out how to re-enable
59
+ // this block if the transition is cancelled for some reason.
60
+ unblock();
61
+ tx.retry();
62
+ },
63
+ };
57
64
 
58
- blocker(autoUnblockingTx);
59
- });
65
+ blocker(autoUnblockingTx);
66
+ });
67
+ } else {
68
+ //https://gist.github.com/MarksCode/64e438c82b0b2a1161e01c88ca0d0355
69
+ navigator.push = (...args: Parameters<typeof push>) => {
70
+ if (window.confirm(promptMessage)) {
71
+ push(...args);
72
+ }
73
+ };
74
+ }
60
75
 
61
76
  window.addEventListener('beforeunload', beforeUnload);
62
77
  return () => {
63
78
  unblock();
79
+ navigator.push = push;
64
80
  window.removeEventListener('beforeunload', beforeUnload);
65
81
  };
66
82
 
@@ -4,6 +4,7 @@ exports[`FormTestBase has matching snapshot 1`] = `
4
4
  <DocumentFragment>
5
5
  <form
6
6
  action="#"
7
+ class="envoc-form-form"
7
8
  />
8
9
  </DocumentFragment>
9
10
  `;
@@ -1,3 +1,4 @@
1
+ import { FormDefaults } from './FormDefaults';
1
2
  import { useFormikContext } from 'formik';
2
3
  import SubmitFormButton from './SubmitFormButton';
3
4
 
@@ -15,13 +16,18 @@ export default function FormActions({
15
16
  return (
16
17
  <>
17
18
  <SubmitFormButton
18
- className="form-actions"
19
+ className={FormDefaults.cssClassPrefix + 'form-actions'}
19
20
  allowPristineSubmit={allowPristineSubmit}
20
21
  disabled={disabled}
21
22
  />
22
23
  <button
23
24
  type="button"
24
- className="form-actions cancel-form-button"
25
+ className={
26
+ FormDefaults.cssClassPrefix +
27
+ 'form-actions ' +
28
+ FormDefaults.cssClassPrefix +
29
+ 'cancel-form-button'
30
+ }
25
31
  disabled={isSubmitting || disabled}
26
32
  onClick={handleCancel || goBack}>
27
33
  Cancel
@@ -0,0 +1 @@
1
+ export const FormDefaults = { cssClassPrefix: 'envoc-form-' };
package/src/Group.tsx CHANGED
@@ -1,4 +1,5 @@
1
1
  import classNames from 'classnames';
2
+ import { FormDefaults } from './FormDefaults';
2
3
  import FieldErrorScrollTarget from './Field/FieldErrorScrollTarget';
3
4
  import { InjectedFieldProps } from './Field/InjectedFieldProps';
4
5
 
@@ -14,6 +15,8 @@ export interface GroupProps extends InjectedFieldProps<any> {
14
15
  helpText?: string | React.ReactNode;
15
16
 
16
17
  disabled?: boolean;
18
+
19
+ required?: boolean;
17
20
  }
18
21
 
19
22
  /** contains standard field bits like a label, helper text, error scroll target, validation message container, etc */
@@ -25,19 +28,31 @@ export default function Group({
25
28
  meta,
26
29
  input,
27
30
  disabled,
31
+ required,
28
32
  }: GroupProps) {
29
33
  return (
30
34
  <div
31
- className={classNames(className, 'group', {
32
- 'is-invalid': meta.error,
33
- disabled: disabled,
35
+ className={classNames(className, FormDefaults.cssClassPrefix + 'group', {
36
+ [FormDefaults.cssClassPrefix + 'invalid']: meta.error,
37
+ [FormDefaults.cssClassPrefix + 'disabled']: disabled,
38
+ [FormDefaults.cssClassPrefix + 'required']: required,
34
39
  })}>
35
40
  <FieldErrorScrollTarget />
36
- {meta.warning && <div className="warning">{meta.warning}</div>}
41
+ {meta.warning && (
42
+ <div className={FormDefaults.cssClassPrefix + 'warning'}>
43
+ {meta.warning}
44
+ </div>
45
+ )}
37
46
  <label htmlFor={input.id}>{label}</label>
38
47
  {children}
39
- {meta.error && <div className="error">{meta.error}</div>}
40
- {helpText && <div className="help">{helpText}</div>}
48
+ {meta.error && (
49
+ <div className={FormDefaults.cssClassPrefix + 'error'}>
50
+ {meta.error}
51
+ </div>
52
+ )}
53
+ {helpText && (
54
+ <div className={FormDefaults.cssClassPrefix + 'help'}>{helpText}</div>
55
+ )}
41
56
  </div>
42
57
  );
43
58
  }
@@ -1,13 +1,13 @@
1
1
  import React, { LegacyRef } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import InputGroupWithRef, { InputGroupProps } from './InputGroup';
4
- import useStandardFormInput from '../Field/useStandardField';
4
+ import { FormDefaults } from '../FormDefaults';
5
5
 
6
6
  // TODO: make className tailwind-able instead of css. make it typeof string ????
7
7
  // or should we just give a div a className and let each project decide? (this seems to be the patern)
8
8
  export interface IconInputGroupProps
9
9
  extends Omit<
10
- InputGroupProps<string | undefined>,
10
+ InputGroupProps<string | undefined | null>,
11
11
  'onChange' | 'type' | 'value'
12
12
  > {
13
13
  type?: 'color' | 'email' | 'search' | 'tel' | 'text' | 'url';
@@ -26,8 +26,11 @@ function IconInputGroup(
26
26
  <InputGroupWithRef
27
27
  icon={icon}
28
28
  ref={ref}
29
- className={classNames(className, 'icon-input-group')}
30
- value={input.value || ''}
29
+ className={classNames(
30
+ className,
31
+ FormDefaults.cssClassPrefix + 'icon-input-group'
32
+ )}
33
+ value={input.value ?? ''}
31
34
  onChange={(e) => {
32
35
  if (!e.target.value) {
33
36
  input.onChange(undefined);
@@ -1,17 +1,22 @@
1
1
  import React, { ChangeEventHandler, LegacyRef } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import { InjectedFieldProps } from '../Field/InjectedFieldProps';
4
+ import { FormDefaults } from '../FormDefaults';
4
5
  import Group, { GroupProps } from '../Group';
5
6
 
6
7
  export interface InputGroupProps<TValue>
7
8
  extends InjectedFieldProps<TValue>,
8
9
  Omit<
9
10
  React.HTMLProps<HTMLInputElement>,
10
- keyof InjectedFieldProps<any> | 'children' | 'className' | 'label'
11
+ | keyof InjectedFieldProps<any>
12
+ | 'children'
13
+ | 'className'
14
+ | 'label'
15
+ | 'value'
11
16
  >,
12
17
  Omit<GroupProps, keyof InjectedFieldProps<any> | 'children'> {
13
18
  onChange: ChangeEventHandler<HTMLInputElement>;
14
- value: string | number;
19
+ value: string | number | null | undefined;
15
20
  icon?: React.ReactNode;
16
21
  }
17
22
 
@@ -23,6 +28,7 @@ function InputGroup<TValue>(
23
28
  label,
24
29
  helpText,
25
30
  className,
31
+ required,
26
32
  disabled,
27
33
  onChange,
28
34
  value,
@@ -35,18 +41,25 @@ function InputGroup<TValue>(
35
41
  <Group
36
42
  input={input}
37
43
  meta={meta}
44
+ required={required}
38
45
  disabled={disabled}
39
46
  label={label}
40
47
  helpText={helpText}
41
- className={classNames(className, 'input-group')}>
48
+ className={classNames(
49
+ className,
50
+ FormDefaults.cssClassPrefix + 'input-group'
51
+ )}>
42
52
  {icon}
43
53
  <input
44
54
  {...input}
45
55
  {...rest}
46
- value={value}
56
+ value={value ?? ''}
47
57
  onChange={onChange}
48
58
  ref={ref}
49
- className={classNames(className, 'input-group')}
59
+ className={classNames(
60
+ className,
61
+ FormDefaults.cssClassPrefix + 'input-group'
62
+ )}
50
63
  />
51
64
  </Group>
52
65
  );
@@ -1,10 +1,11 @@
1
1
  import React, { LegacyRef } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import InputGroup, { InputGroupProps } from './InputGroup';
4
+ import { FormDefaults } from '../FormDefaults';
4
5
 
5
6
  export interface MoneyInputGroupProps
6
7
  extends Omit<
7
- InputGroupProps<number | undefined>,
8
+ InputGroupProps<number | undefined | null>,
8
9
  'onChange' | 'type' | 'value'
9
10
  > {
10
11
  parseFunc?: typeof parseInt | typeof parseFloat;
@@ -24,7 +25,10 @@ function MoneyInputGroup(
24
25
  min={0}
25
26
  {...rest}
26
27
  type="number"
27
- className={classNames(className, 'money-group')}
28
+ className={classNames(
29
+ className,
30
+ FormDefaults.cssClassPrefix + 'money-group'
31
+ )}
28
32
  value={input.value || ''}
29
33
  onChange={(e) => {
30
34
  if (!e.target.value) {