@ultraviolet/form 2.0.0-next.0 → 2.0.0-next.10

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 (32) hide show
  1. package/dist/components/CheckboxField/index.js +45 -49
  2. package/dist/components/CheckboxGroupField/index.js +31 -33
  3. package/dist/components/DateField/index.js +55 -58
  4. package/dist/components/Form/defaultErrors.js +21 -0
  5. package/dist/components/Form/index.js +75 -36
  6. package/dist/components/FormSpy/index.js +23 -0
  7. package/dist/components/NumberInputField/index.js +49 -40
  8. package/dist/components/RadioField/index.js +38 -42
  9. package/dist/components/RadioGroupField/index.js +32 -31
  10. package/dist/components/SelectInputField/index.js +74 -82
  11. package/dist/components/SelectableCardField/index.js +45 -43
  12. package/dist/components/Submit/index.js +19 -32
  13. package/dist/components/SubmitErrorAlert/index.js +12 -20
  14. package/dist/components/TagInputField/index.js +25 -26
  15. package/dist/components/TextInputField/index.js +104 -108
  16. package/dist/components/TimeField/index.js +44 -47
  17. package/dist/components/ToggleField/index.js +39 -52
  18. package/dist/constants.js +3 -0
  19. package/dist/helpers/pickValidators.js +1 -4
  20. package/dist/hooks/useField.js +46 -0
  21. package/dist/hooks/useFieldArray.js +35 -0
  22. package/dist/hooks/useForm.js +36 -0
  23. package/dist/hooks/useFormField.js +26 -27
  24. package/dist/hooks/useFormState.js +37 -0
  25. package/dist/hooks/useOnFieldChange.js +14 -23
  26. package/dist/hooks/useValidation.js +14 -20
  27. package/dist/index.d.ts +257 -539
  28. package/dist/index.js +6 -5
  29. package/dist/providers/ErrorContext/index.js +12 -51
  30. package/dist/validators/maxDate.js +1 -4
  31. package/dist/validators/minDate.js +1 -4
  32. package/package.json +12 -10
@@ -1,75 +1,71 @@
1
1
  import { Checkbox } from '@ultraviolet/ui';
2
- import { forwardRef } from 'react';
2
+ import { useController } from 'react-hook-form';
3
3
  import { jsx } from '@emotion/react/jsx-runtime';
4
4
  import { useErrors } from '../../providers/ErrorContext/index.js';
5
- import { useFormField } from '../../hooks/useFormField.js';
6
5
 
7
- const CheckboxField = /*#__PURE__*/forwardRef((_ref, ref) => {
8
- let {
9
- validate,
10
- name,
11
- label = '',
12
- size,
13
- progress,
14
- disabled,
15
- required,
16
- className,
17
- children,
18
- onChange,
19
- onBlur,
20
- onFocus,
21
- value,
22
- helper,
23
- tooltip,
24
- 'data-testid': dataTestId
25
- } = _ref;
6
+ const CheckboxField = ({
7
+ name,
8
+ label,
9
+ size,
10
+ progress,
11
+ disabled,
12
+ required,
13
+ className,
14
+ children,
15
+ onChange,
16
+ onBlur,
17
+ onFocus,
18
+ rules,
19
+ helper,
20
+ tooltip,
21
+ 'data-testid': dataTestId,
22
+ value,
23
+ shouldUnregister = false
24
+ }) => {
26
25
  const {
27
26
  getError
28
27
  } = useErrors();
29
28
  const {
30
- input,
31
- meta
32
- } = useFormField(name, {
33
- disabled,
34
- required,
35
- type: 'checkbox',
36
- validate,
37
- value
38
- });
39
- const error = getError({
40
- label,
41
- meta: meta,
29
+ field,
30
+ fieldState: {
31
+ error
32
+ }
33
+ } = useController({
42
34
  name,
43
- value: input.value ?? input.checked
35
+ disabled,
36
+ shouldUnregister,
37
+ rules: {
38
+ required,
39
+ ...rules
40
+ }
44
41
  });
45
42
  return jsx(Checkbox, {
46
- name: input.name,
43
+ name: field.name,
47
44
  onChange: event => {
48
- input.onChange(event);
49
- onChange?.(event);
45
+ field.onChange(value ? [...(field.value ?? []), value] : event.target.checked);
46
+ onChange?.(event.target.checked);
50
47
  },
51
48
  onBlur: event => {
52
- input.onBlur(event);
49
+ field.onBlur();
53
50
  onBlur?.(event);
54
51
  },
55
- onFocus: event => {
56
- input.onFocus(event);
57
- onFocus?.(event);
58
- },
52
+ onFocus: onFocus,
59
53
  size: size,
60
54
  progress: progress,
61
- disabled: disabled,
62
- checked: input.checked,
63
- error: error,
64
- helper: helper,
65
- ref: ref,
55
+ disabled: field.disabled,
56
+ checked: Array.isArray(field.value) ? field.value.includes(value) : !!field.value,
57
+ error: getError({
58
+ label: label ?? ''
59
+ }, error),
60
+ ref: field.ref,
66
61
  className: className,
67
- value: input.value,
68
62
  required: required,
69
63
  "data-testid": dataTestId,
64
+ helper: helper,
70
65
  tooltip: tooltip,
66
+ value: value,
71
67
  children: children
72
68
  });
73
- });
69
+ };
74
70
 
75
71
  export { CheckboxField };
@@ -1,51 +1,49 @@
1
1
  import { CheckboxGroup } from '@ultraviolet/ui';
2
- import { useFieldArray } from 'react-final-form-arrays';
2
+ import { useController } from 'react-hook-form';
3
3
  import { jsx } from '@emotion/react/jsx-runtime';
4
4
  import { useErrors } from '../../providers/ErrorContext/index.js';
5
5
 
6
- const CheckboxGroupField = _ref => {
7
- let {
8
- legend,
9
- value,
10
- className,
11
- helper,
12
- direction,
13
- children,
14
- onChange,
15
- error: customError,
16
- name,
17
- required = false
18
- } = _ref;
6
+ const CheckboxGroupField = ({
7
+ legend,
8
+ className,
9
+ helper,
10
+ direction,
11
+ children,
12
+ onChange,
13
+ label = '',
14
+ error: customError,
15
+ name,
16
+ required = false,
17
+ shouldUnregister = false
18
+ }) => {
19
19
  const {
20
20
  getError
21
21
  } = useErrors();
22
22
  const {
23
- fields,
24
- meta
25
- } = useFieldArray(name, {
26
- type: 'checkbox',
27
- value,
28
- validate: localValue => required && localValue?.length === 0 ? 'Required' : undefined
29
- });
30
- const error = getError({
31
- label: legend,
32
- meta,
33
- value: fields.value,
34
- name
23
+ field,
24
+ fieldState: {
25
+ error
26
+ }
27
+ } = useController({
28
+ name,
29
+ shouldUnregister
35
30
  });
36
31
  return jsx(CheckboxGroup, {
37
32
  legend: legend,
38
- name: fields.name,
39
- value: fields.value,
33
+ name: name,
34
+ value: field.value,
40
35
  onChange: event => {
41
- if (fields.value?.includes(event.currentTarget.value)) {
42
- fields.remove(fields.value.indexOf(event.currentTarget?.value));
36
+ const fieldValue = field.value;
37
+ if (fieldValue?.includes(event.currentTarget.value)) {
38
+ field.onChange(fieldValue?.filter(currentValue => currentValue !== event.currentTarget.value));
43
39
  } else {
44
- fields.push(event.currentTarget.value);
40
+ field.onChange([...field.value, event.currentTarget.value]);
45
41
  }
46
- onChange?.(event);
42
+ onChange?.(event.currentTarget.value);
47
43
  },
48
- error: error ?? customError,
44
+ error: getError({
45
+ label
46
+ }, error) ?? customError,
49
47
  className: className,
50
48
  direction: direction,
51
49
  helper: helper,
@@ -1,96 +1,93 @@
1
1
  import { DateInput } from '@ultraviolet/ui';
2
+ import { useController } from 'react-hook-form';
3
+ import { maxDateValidator } from '../../validators/maxDate.js';
4
+ import { minDateValidator } from '../../validators/minDate.js';
2
5
  import { jsx } from '@emotion/react/jsx-runtime';
3
6
  import { useErrors } from '../../providers/ErrorContext/index.js';
4
- import { useFormField } from '../../hooks/useFormField.js';
5
7
 
6
8
  const parseDate = value => typeof value === 'string' ? new Date(value) : value;
7
9
  const isEmpty = value => typeof value === 'string' ? value === '' : value === undefined;
8
- const DateField = _ref => {
9
- let {
10
- required,
11
- name,
12
- label = '',
13
- validate,
14
- format,
15
- locale,
16
- maxDate,
17
- minDate,
18
- initialValue,
19
- disabled,
20
- value: inputVal,
21
- onChange,
22
- onBlur,
23
- onFocus,
24
- formatOnBlur,
25
- autoFocus = false,
26
- excludeDates,
27
- selectsRange,
28
- 'data-testid': dataTestId
29
- } = _ref;
10
+ const DateField = ({
11
+ required,
12
+ name,
13
+ label = '',
14
+ format,
15
+ locale,
16
+ maxDate,
17
+ minDate,
18
+ disabled,
19
+ onChange,
20
+ onBlur,
21
+ onFocus,
22
+ rules,
23
+ autoFocus = false,
24
+ excludeDates,
25
+ selectsRange,
26
+ 'data-testid': dataTestId,
27
+ shouldUnregister = false
28
+ }) => {
30
29
  const {
31
30
  getError
32
31
  } = useErrors();
33
32
  const {
34
- input,
35
- meta
36
- } = useFormField(name, {
37
- disabled,
38
- formatOnBlur,
39
- initialValue,
40
- maxDate,
41
- minDate,
42
- required,
43
- validate,
44
- value: inputVal
45
- });
46
- const error = getError({
47
- label,
48
- maxDate,
49
- meta: meta,
50
- minDate,
33
+ field,
34
+ fieldState: {
35
+ error
36
+ }
37
+ } = useController({
51
38
  name,
52
- value: input.value
39
+ shouldUnregister,
40
+ rules: {
41
+ ...rules,
42
+ validate: {
43
+ ...rules?.validate,
44
+ minDate: minDateValidator(minDate),
45
+ maxDate: maxDateValidator(maxDate)
46
+ },
47
+ required
48
+ }
53
49
  });
54
50
  return jsx(DateInput, {
51
+ name: field.name,
55
52
  label: label,
53
+ value: field.value,
56
54
  format: format || (value => value ? parseDate(value).toLocaleDateString() : ''),
57
55
  locale: locale,
58
56
  required: required,
59
- value: input.value,
60
57
  onChange: val => {
61
58
  if (val && val instanceof Date) {
62
59
  onChange?.(val);
63
60
  const newDate = parseDate(val);
64
- if (isEmpty(input.value)) {
65
- input.onChange(newDate);
61
+ if (isEmpty(field.value)) {
62
+ field.onChange(newDate);
66
63
  return;
67
64
  }
68
- const currentDate = parseDate(input.value);
65
+ const currentDate = parseDate(field.value);
69
66
  newDate.setHours(currentDate.getHours(), currentDate.getMinutes());
70
- input.onChange(newDate);
67
+ field.onChange(newDate);
71
68
  } else if (Array.isArray(val)) {
72
- input.onChange(val);
69
+ field.onChange(val);
73
70
  }
74
71
  },
75
72
  onBlur: e => {
76
- input.onBlur(e);
73
+ field.onBlur();
77
74
  onBlur?.(e);
78
75
  },
79
- onFocus: e => {
80
- input.onFocus(e);
81
- onFocus?.(e);
82
- },
76
+ onFocus: onFocus,
83
77
  maxDate: maxDate,
84
78
  minDate: minDate,
85
- startDate: selectsRange ? input.value[0] : undefined,
86
- endDate: selectsRange ? input.value[1] : undefined,
87
- error: error,
79
+ error: getError({
80
+ minDate,
81
+ maxDate,
82
+ label
83
+ }, error),
88
84
  disabled: disabled,
89
85
  autoFocus: autoFocus,
90
- name: input.name,
91
- "data-testid": dataTestId,
92
86
  excludeDates: excludeDates,
93
- selectsRange: selectsRange
87
+ selectsRange: selectsRange,
88
+ "data-testid": dataTestId,
89
+ startDate: selectsRange && Array.isArray(field.value) ? field.value[0] : undefined,
90
+ endDate: selectsRange && Array.isArray(field.value) ? field.value[1] : undefined
94
91
  });
95
92
  };
96
93
 
@@ -0,0 +1,21 @@
1
+ const defaultErrors = {
2
+ maxDate: () => '',
3
+ maxLength: () => '',
4
+ minLength: () => '',
5
+ max: () => '',
6
+ min: () => '',
7
+ required: () => '',
8
+ pattern: () => '',
9
+ deps: () => '',
10
+ value: () => '',
11
+ onBlur: () => '',
12
+ disabled: () => '',
13
+ onChange: () => '',
14
+ validate: () => '',
15
+ setValueAs: () => '',
16
+ valueAsDate: () => '',
17
+ valueAsNumber: () => '',
18
+ shouldUnregister: () => ''
19
+ };
20
+
21
+ export { defaultErrors };
@@ -1,43 +1,82 @@
1
- import arrayMutators from 'final-form-arrays';
2
- import { Form as Form$1 } from 'react-final-form';
1
+ import React, { useMemo } from 'react';
2
+ import { useForm, FormProvider } from 'react-hook-form';
3
+ import { FORM_ERROR } from '../../constants.js';
4
+ import { defaultErrors } from './defaultErrors.js';
3
5
  import { jsx } from '@emotion/react/jsx-runtime';
4
6
  import { ErrorProvider } from '../../providers/ErrorContext/index.js';
5
7
 
6
- const Form = _ref => {
7
- let {
8
- children,
9
- onRawSubmit,
10
- errors,
11
- initialValues,
12
- validateOnBlur,
13
- validate,
14
- name,
15
- render,
16
- mutators,
17
- keepDirtyOnReinitialize,
18
- className
19
- } = _ref;
20
- return jsx(Form$1, {
21
- initialValues: initialValues,
22
- validateOnBlur: validateOnBlur,
23
- validate: validate,
24
- mutators: {
25
- ...arrayMutators,
26
- ...mutators
27
- },
28
- onSubmit: onRawSubmit,
29
- render: render ?? (renderProps => jsx(ErrorProvider, {
30
- errors: errors,
31
- children: jsx("form", {
32
- noValidate: true,
33
- name: name,
34
- onSubmit: renderProps.handleSubmit,
35
- className: className,
36
- children: typeof children === 'function' ? children(renderProps) : children
8
+ const FormSubmitContext = /*#__PURE__*/React.createContext({});
9
+ const Form = ({
10
+ children,
11
+ methods: methodsProp,
12
+ initialValues,
13
+ errors,
14
+ onRawSubmit,
15
+ name
16
+ }) => {
17
+ const methodsHook = useForm({
18
+ defaultValues: initialValues,
19
+ mode: 'onChange'
20
+ });
21
+ const methods = !methodsProp ? methodsHook : methodsProp;
22
+ const handleSubmit = methods.handleSubmit(async values => {
23
+ const result = await onRawSubmit(values, {
24
+ reset: methods.reset,
25
+ resetFieldState: methods.resetField,
26
+ restart: () => methods.reset(initialValues),
27
+ change: methods.setValue
28
+ });
29
+ if (result === null) {
30
+ methods.setError('root.submit', {
31
+ type: 'custom'
32
+ });
33
+ return;
34
+ }
35
+ if (result && typeof result !== 'boolean' && typeof result !== 'number') {
36
+ methods.setError('root.submit', {
37
+ type: 'custom',
38
+ message: typeof result === 'object' ? result[FORM_ERROR] : result
39
+ });
40
+ }
41
+ });
42
+ const formSubmitContextValue = useMemo(() => ({
43
+ handleSubmit
44
+ }), [handleSubmit]);
45
+ return jsx(FormProvider, {
46
+ ...methods,
47
+ children: jsx(FormSubmitContext.Provider, {
48
+ value: formSubmitContextValue,
49
+ children: jsx(ErrorProvider, {
50
+ errors: {
51
+ ...defaultErrors,
52
+ ...errors
53
+ },
54
+ children: jsx("form", {
55
+ onSubmit: async e => {
56
+ e.preventDefault();
57
+ e.stopPropagation();
58
+ await handleSubmit(e);
59
+ },
60
+ name: name,
61
+ children: typeof children === 'function' ? children({
62
+ values: methods.watch(),
63
+ hasValidationErrors: !methods.formState.isValid,
64
+ errors: methods.formState.errors,
65
+ submitting: methods.formState.isSubmitting,
66
+ pristine: !methods.formState.isDirty,
67
+ handleSubmit,
68
+ submitError: !!methods.formState.errors?.root?.['submit'],
69
+ valid: methods.formState.isValid,
70
+ form: {
71
+ change: methods.setValue,
72
+ reset: methods.reset,
73
+ submit: handleSubmit
74
+ }
75
+ }) : children
76
+ })
37
77
  })
38
- })),
39
- keepDirtyOnReinitialize: keepDirtyOnReinitialize
78
+ })
40
79
  });
41
80
  };
42
81
 
43
- export { Form };
82
+ export { Form, FormSubmitContext };
@@ -0,0 +1,23 @@
1
+ import { useEffect } from 'react';
2
+ import { useForm } from 'react-hook-form';
3
+
4
+ /**
5
+ * @deprecated
6
+ */
7
+ const FormSpy = _ref => {
8
+ let {
9
+ onChange
10
+ } = _ref;
11
+ const {
12
+ watch
13
+ } = useForm();
14
+ useEffect(() => {
15
+ const subscription = watch(values => onChange?.({
16
+ values
17
+ }));
18
+ return () => subscription.unsubscribe();
19
+ }, [watch, onChange]);
20
+ return null;
21
+ };
22
+
23
+ export { FormSpy };
@@ -1,54 +1,59 @@
1
1
  import { NumberInput } from '@ultraviolet/ui';
2
+ import { useController } from 'react-hook-form';
2
3
  import { jsx } from '@emotion/react/jsx-runtime';
3
- import { useFormField } from '../../hooks/useFormField.js';
4
+ import { useErrors } from '../../providers/ErrorContext/index.js';
4
5
 
5
- const NumberInputField = _ref => {
6
- let {
7
- disabled,
8
- maxValue,
9
- minValue,
10
- name,
11
- onChange,
12
- onBlur,
13
- onFocus,
14
- onMaxCrossed,
15
- onMinCrossed,
16
- required,
17
- size,
18
- step,
19
- text,
20
- validate,
21
- value,
22
- className,
23
- 'data-testid': dataTestId
24
- } = _ref;
6
+ const NumberInputField = ({
7
+ disabled,
8
+ maxValue,
9
+ minValue,
10
+ name,
11
+ onChange,
12
+ onBlur,
13
+ onFocus,
14
+ onMaxCrossed,
15
+ onMinCrossed,
16
+ required,
17
+ size,
18
+ step,
19
+ text,
20
+ rules,
21
+ className,
22
+ label,
23
+ shouldUnregister = false
24
+ }) => {
25
+ const {
26
+ getError
27
+ } = useErrors();
25
28
  const {
26
- input
27
- } = useFormField(name, {
28
- disabled,
29
- required,
30
- type: 'number',
31
- validate,
32
- value,
33
- defaultValue: 0,
34
- max: maxValue,
35
- min: minValue
29
+ field,
30
+ fieldState: {
31
+ error
32
+ }
33
+ } = useController({
34
+ name,
35
+ shouldUnregister,
36
+ rules: {
37
+ max: maxValue,
38
+ min: minValue,
39
+ required,
40
+ ...rules
41
+ }
36
42
  });
37
43
  return jsx(NumberInput, {
38
- name: name,
44
+ name: field.name,
45
+ value: field.value,
39
46
  disabled: disabled,
40
47
  onBlur: event => {
41
- input.onBlur(event);
48
+ field.onBlur();
42
49
  onBlur?.(event);
43
50
  },
44
51
  onChange: event => {
45
- input.onChange(event);
52
+ // React hook form doesnt allow undefined values after definition https://react-hook-form.com/docs/usecontroller/controller (that make sense)
53
+ field.onChange(event ?? null);
46
54
  onChange?.(event);
47
55
  },
48
- onFocus: event => {
49
- input.onFocus(event);
50
- onFocus?.(event);
51
- },
56
+ onFocus: onFocus,
52
57
  maxValue: maxValue,
53
58
  minValue: minValue,
54
59
  onMinCrossed: onMinCrossed,
@@ -56,9 +61,13 @@ const NumberInputField = _ref => {
56
61
  size: size,
57
62
  step: step,
58
63
  text: text,
59
- value: input.value,
60
64
  className: className,
61
- "data-testid": dataTestId
65
+ label: label,
66
+ error: getError({
67
+ label: label ?? '',
68
+ max: maxValue,
69
+ min: minValue
70
+ }, error)
62
71
  });
63
72
  };
64
73