@conform-to/react 0.9.0 → 1.0.0-pre.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/helpers.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
2
- export { INTENT, VALIDATION_SKIPPED, VALIDATION_UNDEFINED } from '@conform-to/dom';
3
2
 
4
3
  /**
5
4
  * Cleanup `undefined` from the dervied props
@@ -13,31 +12,25 @@ function cleanup(props) {
13
12
  }
14
13
  return props;
15
14
  }
16
- function getFormElementProps(config) {
17
- var _options$ariaAttribut, _config$error, _config$error2;
15
+ function getAriaAttributes(metadata) {
18
16
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
19
- var hasAriaAttributes = (_options$ariaAttribut = options.ariaAttributes) !== null && _options$ariaAttribut !== void 0 ? _options$ariaAttribut : true;
17
+ if (typeof options.ariaAttributes !== 'undefined' && !options.ariaAttributes) {
18
+ return {};
19
+ }
20
20
  return cleanup({
21
- id: config.id,
22
- name: config.name,
23
- form: config.form,
24
- 'aria-invalid': hasAriaAttributes && config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length ? true : undefined,
25
- 'aria-describedby': hasAriaAttributes ? [config.errorId && (_config$error2 = config.error) !== null && _config$error2 !== void 0 && _config$error2.length ? config.errorId : undefined, config.descriptionId && options.ariaAttributes !== false && options.description ? config.descriptionId : undefined].reduce((result, id) => {
26
- if (!result) {
27
- return id;
28
- }
29
- if (!id) {
30
- return result;
31
- }
32
- return "".concat(result, " ").concat(id);
33
- }) : undefined
21
+ 'aria-invalid': !metadata.valid || undefined,
22
+ 'aria-describedby': metadata.valid ? options.description ? metadata.descriptionId : undefined : "".concat(metadata.errorId, " ").concat(options.description ? metadata.descriptionId : '').trim()
34
23
  });
35
24
  }
36
- function getFormControlProps(config, options) {
37
- return cleanup(_objectSpread2(_objectSpread2({}, getFormElementProps(config, options)), {}, {
38
- required: config.required,
39
- autoFocus: config.initialError && Object.entries(config.initialError).length > 0 ? true : undefined
40
- }, options !== null && options !== void 0 && options.hidden ? hiddenProps : undefined));
25
+ function getFormControlProps(metadata, options) {
26
+ var _metadata$constraint;
27
+ return cleanup(_objectSpread2(_objectSpread2({
28
+ id: metadata.id,
29
+ name: metadata.name,
30
+ form: metadata.formId,
31
+ required: ((_metadata$constraint = metadata.constraint) === null || _metadata$constraint === void 0 ? void 0 : _metadata$constraint.required) || undefined,
32
+ autoFocus: !metadata.valid || undefined
33
+ }, options !== null && options !== void 0 && options.hidden ? hiddenProps : undefined), getAriaAttributes(metadata, options)));
41
34
  }
42
35
  var hiddenProps = {
43
36
  /**
@@ -58,54 +51,83 @@ var hiddenProps = {
58
51
  tabIndex: -1,
59
52
  'aria-hidden': true
60
53
  };
61
- function input(config) {
54
+ function input(field) {
55
+ var _field$constraint, _field$constraint2, _field$constraint3, _field$constraint4, _field$constraint5, _field$constraint6, _field$constraint7;
62
56
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
63
- var props = _objectSpread2(_objectSpread2({}, getFormControlProps(config, options)), {}, {
57
+ var props = _objectSpread2(_objectSpread2({}, getFormControlProps(field, options)), {}, {
64
58
  type: options.type,
65
- minLength: config.minLength,
66
- maxLength: config.maxLength,
67
- min: config.min,
68
- max: config.max,
69
- step: config.step,
70
- pattern: config.pattern,
71
- multiple: config.multiple
59
+ minLength: (_field$constraint = field.constraint) === null || _field$constraint === void 0 ? void 0 : _field$constraint.minLength,
60
+ maxLength: (_field$constraint2 = field.constraint) === null || _field$constraint2 === void 0 ? void 0 : _field$constraint2.maxLength,
61
+ min: (_field$constraint3 = field.constraint) === null || _field$constraint3 === void 0 ? void 0 : _field$constraint3.min,
62
+ max: (_field$constraint4 = field.constraint) === null || _field$constraint4 === void 0 ? void 0 : _field$constraint4.max,
63
+ step: (_field$constraint5 = field.constraint) === null || _field$constraint5 === void 0 ? void 0 : _field$constraint5.step,
64
+ pattern: (_field$constraint6 = field.constraint) === null || _field$constraint6 === void 0 ? void 0 : _field$constraint6.pattern,
65
+ multiple: (_field$constraint7 = field.constraint) === null || _field$constraint7 === void 0 ? void 0 : _field$constraint7.multiple
72
66
  });
73
67
  if (options.type === 'checkbox' || options.type === 'radio') {
74
68
  var _options$value;
75
69
  props.value = (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : 'on';
76
- props.defaultChecked = config.defaultValue === props.value;
70
+ props.defaultChecked = typeof field.initialValue === 'boolean' ? field.initialValue : field.initialValue === props.value;
77
71
  } else if (options.type !== 'file') {
78
- props.defaultValue = config.defaultValue;
72
+ var _field$initialValue;
73
+ props.defaultValue = (_field$initialValue = field.initialValue) === null || _field$initialValue === void 0 ? void 0 : _field$initialValue.toString();
79
74
  }
80
75
  return cleanup(props);
81
76
  }
82
- function select(config, options) {
83
- return cleanup(_objectSpread2(_objectSpread2({}, getFormControlProps(config, options)), {}, {
84
- defaultValue: config.defaultValue,
85
- multiple: config.multiple
77
+ function select(metadata, options) {
78
+ var _metadata$initialValu, _metadata$constraint2;
79
+ return cleanup(_objectSpread2(_objectSpread2({}, getFormControlProps(metadata, options)), {}, {
80
+ defaultValue: (_metadata$initialValu = metadata.initialValue) === null || _metadata$initialValu === void 0 ? void 0 : _metadata$initialValu.toString(),
81
+ multiple: (_metadata$constraint2 = metadata.constraint) === null || _metadata$constraint2 === void 0 ? void 0 : _metadata$constraint2.multiple
86
82
  }));
87
83
  }
88
- function textarea(config, options) {
89
- return cleanup(_objectSpread2(_objectSpread2({}, getFormControlProps(config, options)), {}, {
90
- defaultValue: config.defaultValue,
91
- minLength: config.minLength,
92
- maxLength: config.maxLength
84
+ function textarea(metadata, options) {
85
+ var _metadata$initialValu2, _metadata$constraint3, _metadata$constraint4;
86
+ return cleanup(_objectSpread2(_objectSpread2({}, getFormControlProps(metadata, options)), {}, {
87
+ defaultValue: (_metadata$initialValu2 = metadata.initialValue) === null || _metadata$initialValu2 === void 0 ? void 0 : _metadata$initialValu2.toString(),
88
+ minLength: (_metadata$constraint3 = metadata.constraint) === null || _metadata$constraint3 === void 0 ? void 0 : _metadata$constraint3.minLength,
89
+ maxLength: (_metadata$constraint4 = metadata.constraint) === null || _metadata$constraint4 === void 0 ? void 0 : _metadata$constraint4.maxLength
93
90
  }));
94
91
  }
95
- function fieldset(config, options) {
96
- return getFormElementProps(config, options);
92
+ function form(metadata, options) {
93
+ var onSubmit = options === null || options === void 0 ? void 0 : options.onSubmit;
94
+ var onReset = options === null || options === void 0 ? void 0 : options.onReset;
95
+ return cleanup(_objectSpread2({
96
+ id: metadata.id,
97
+ onSubmit: typeof onSubmit !== 'function' ? metadata.onSubmit : event => {
98
+ var context = metadata.onSubmit(event);
99
+ if (!event.defaultPrevented) {
100
+ onSubmit(event, context);
101
+ }
102
+ },
103
+ onReset: typeof onReset !== 'function' ? metadata.onReset : event => {
104
+ metadata.onReset(event);
105
+ onReset(event);
106
+ },
107
+ noValidate: metadata.noValidate
108
+ }, getAriaAttributes(metadata, options)));
97
109
  }
98
- function collection(config, options) {
99
- return options.options.map(value => cleanup(_objectSpread2(_objectSpread2({}, getFormControlProps(config, options)), {}, {
100
- id: config.id ? "".concat(config.id, "-").concat(value) : undefined,
101
- type: options.type,
102
- value,
103
- defaultChecked: options.type === 'checkbox' && Array.isArray(config.defaultValue) ? config.defaultValue.includes(value) : config.defaultValue === value,
104
- // The required attribute doesn't make sense for checkbox group
105
- // As it would require all checkboxes to be checked instead of at least one
106
- // overriden with `undefiend` so it gets cleaned up
107
- required: options.type === 'checkbox' ? undefined : config.required
108
- })));
110
+ function fieldset(metadata, options) {
111
+ return cleanup(_objectSpread2({
112
+ id: metadata.id,
113
+ name: metadata.name,
114
+ form: metadata.formId
115
+ }, getAriaAttributes(metadata, options)));
116
+ }
117
+ function collection(metadata, options) {
118
+ return options.options.map(value => {
119
+ var _metadata$constraint5;
120
+ return cleanup(_objectSpread2(_objectSpread2({}, getFormControlProps(metadata, options)), {}, {
121
+ id: "".concat(metadata.id, "-").concat(value),
122
+ type: options.type,
123
+ value,
124
+ defaultChecked: options.type === 'checkbox' && Array.isArray(metadata.initialValue) ? metadata.initialValue.includes(value) : metadata.initialValue === value,
125
+ // The required attribute doesn't make sense for checkbox group
126
+ // As it would require all checkboxes to be checked instead of at least one
127
+ // It is overriden with `undefiend` so it could be cleaned upW properly
128
+ required: options.type === 'checkbox' ? undefined : (_metadata$constraint5 = metadata.constraint) === null || _metadata$constraint5 === void 0 ? void 0 : _metadata$constraint5.required
129
+ }));
130
+ });
109
131
  }
110
132
 
111
- export { collection, fieldset, hiddenProps, input, select, textarea };
133
+ export { collection, fieldset, form, hiddenProps, input, select, textarea };
package/hooks.d.ts CHANGED
@@ -1,208 +1,54 @@
1
- import { type FieldConstraint, type FieldElement, type FieldsetConstraint, type Submission, type KeysOf, type ResolveType, getFormEncType, getFormMethod, parseIntent } from '@conform-to/dom';
2
- import { type FormEvent, type RefObject } from 'react';
3
- export type Primitive = null | undefined | string | number | boolean | Date;
4
- export interface FieldConfig<Schema> extends FieldConstraint<Schema> {
5
- id?: string;
6
- name: string;
7
- defaultValue?: FieldValue<Schema>;
8
- initialError?: Record<string, string[]>;
9
- form?: string;
10
- descriptionId?: string;
11
- errorId?: string;
12
- /**
13
- * The frist error of the field
14
- */
15
- error?: string;
16
- /**
17
- * All of the field errors
18
- */
19
- errors?: string[];
20
- }
21
- export type FieldValue<Schema> = Schema extends Primitive ? string : Schema extends File ? File : Schema extends Array<infer InnerType> ? Array<FieldValue<InnerType>> : unknown extends Schema ? any : Record<string, any> extends Schema ? {
22
- [Key in KeysOf<Schema>]?: FieldValue<ResolveType<Schema, Key>>;
23
- } : any;
24
- type SubmissionResult = {
25
- intent: Submission['intent'];
26
- payload: Submission['payload'] | null;
27
- error: Submission['error'];
28
- };
29
- export interface FormConfig<Output extends Record<string, any>, Input extends Record<string, any> = Output> {
1
+ import { type UnionKeyof, type UnionKeyType, type FieldName, type Form, type FormOptions } from '@conform-to/dom';
2
+ import { useEffect } from 'react';
3
+ import { type FormMetadata, type FieldMetadata, type Pretty } from './context';
4
+ /**
5
+ * useLayoutEffect is client-only.
6
+ * This basically makes it a no-op on server
7
+ */
8
+ export declare const useSafeLayoutEffect: typeof useEffect;
9
+ export declare function useFormId(preferredId?: string): string;
10
+ export declare function useForm<Schema extends Record<string, any>>(options: Pretty<FormOptions<Schema> & {
30
11
  /**
31
12
  * If the form id is provided, Id for label,
32
13
  * input and error elements will be derived.
33
14
  */
34
15
  id?: string;
35
16
  /**
36
- * A form ref object. Conform will fallback to its own ref object if it is not provided.
37
- */
38
- ref?: RefObject<HTMLFormElement>;
39
- /**
40
- * Define when conform should start validation.
41
- * Support "onSubmit", "onInput", "onBlur".
42
- *
43
- * @default "onSubmit"
44
- */
45
- shouldValidate?: 'onSubmit' | 'onBlur' | 'onInput';
46
- /**
47
- * Define when conform should revalidate again.
48
- * Support "onSubmit", "onInput", "onBlur".
49
- *
50
- * @default shouldValidate, or "onSubmit" if shouldValidate is not provided.
51
- */
52
- shouldRevalidate?: 'onSubmit' | 'onBlur' | 'onInput';
53
- /**
54
- * An object representing the initial value of the form.
55
- */
56
- defaultValue?: FieldValue<Input>;
57
- /**
58
- * An object describing the result of the last submission
59
- */
60
- lastSubmission?: SubmissionResult;
61
- /**
62
- * An object describing the constraint of each field
63
- */
64
- constraint?: FieldsetConstraint<Input>;
65
- /**
66
- * Enable native validation before hydation.
67
- *
68
- * Default to `false`.
69
- */
70
- fallbackNative?: boolean;
71
- /**
72
- * Accept form submission regardless of the form validity.
17
+ * Enable constraint validation before the dom is hydated.
73
18
  *
74
- * Default to `false`.
19
+ * Default to `true`.
75
20
  */
76
- noValidate?: boolean;
77
- /**
78
- * A function to be called when the form should be (re)validated.
79
- */
80
- onValidate?: ({ form, formData, }: {
81
- form: HTMLFormElement;
82
- formData: FormData;
83
- }) => Submission | Submission<Output>;
84
- /**
85
- * The submit event handler of the form. It will be called
86
- * only when the form is considered valid.
87
- */
88
- onSubmit?: (event: FormEvent<HTMLFormElement>, context: {
89
- formData: FormData;
90
- submission: Submission;
91
- action: string;
92
- encType: ReturnType<typeof getFormEncType>;
93
- method: ReturnType<typeof getFormMethod>;
94
- }) => void;
95
- }
96
- /**
97
- * Properties to be applied to the form element
98
- */
99
- interface FormProps {
100
- id?: string;
101
- ref: RefObject<HTMLFormElement>;
102
- onSubmit: (event: FormEvent<HTMLFormElement>) => void;
103
- noValidate: boolean;
104
- 'aria-invalid'?: 'true';
105
- 'aria-describedby'?: string;
106
- }
107
- interface Form {
108
- id?: string;
109
- errorId?: string;
110
- error: string | undefined;
111
- errors: string[];
112
- ref: RefObject<HTMLFormElement>;
113
- props: FormProps;
114
- }
115
- /**
116
- * Returns properties required to hook into form events.
117
- * Applied custom validation and define when error should be reported.
118
- *
119
- * @see https://conform.guide/api/react#useform
120
- */
121
- export declare function useForm<Output extends Record<string, any>, Input extends Record<string, any> = Output>(config?: FormConfig<Output, Input>): [Form, Fieldset<Input>];
122
- /**
123
- * A set of field configuration
124
- */
125
- export type Fieldset<Schema extends Record<string, any> | undefined> = {
126
- [Key in KeysOf<Schema>]-?: FieldConfig<ResolveType<Schema, Key>>;
21
+ defaultNoValidate?: boolean;
22
+ }>): {
23
+ form: FormMetadata<Schema>;
24
+ context: Form<Schema>;
25
+ fields: Pretty<FieldsetMetadata<Schema>>;
127
26
  };
128
- export interface FieldsetConfig<Schema extends Record<string, any> | undefined> {
129
- /**
130
- * The prefix used to generate the name of nested fields.
131
- */
132
- name?: string;
133
- /**
134
- * An object representing the initial value of the fieldset.
135
- */
136
- defaultValue?: FieldValue<Schema>;
137
- /**
138
- * An object describing the initial error of each field
139
- */
140
- initialError?: Record<string, string[]>;
141
- /**
142
- * An object describing the constraint of each field
143
- */
144
- constraint?: FieldsetConstraint<Schema>;
145
- /**
146
- * The id of the form, connecting each field to a form remotely
147
- */
148
- form?: string;
149
- }
150
- /**
151
- * Returns all the information about the fieldset.
152
- *
153
- * @see https://conform.guide/api/react#usefieldset
154
- */
155
- export declare function useFieldset<Schema extends Record<string, any> | undefined>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldsetConfig<Schema>): Fieldset<Schema>;
156
- export declare function useFieldset<Schema extends Record<string, any> | undefined>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Schema>): Fieldset<Schema>;
157
- /**
158
- * Returns a list of key and field config.
159
- *
160
- * @see https://conform.guide/api/react#usefieldlist
161
- */
162
- export declare function useFieldList<Schema extends Array<any> | undefined>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Schema>): Array<{
163
- key: string;
164
- } & FieldConfig<Schema extends Array<infer Item> ? Item : never>>;
165
- interface InputControl {
166
- change: (eventOrValue: {
167
- target: {
168
- value: string;
169
- };
170
- } | string | boolean) => void;
171
- focus: () => void;
172
- blur: () => void;
173
- }
174
- /**
175
- * Returns a ref object and a set of helpers that dispatch corresponding dom event.
176
- *
177
- * @see https://conform.guide/api/react#useinputevent
178
- */
179
- export declare function useInputEvent(options: {
180
- ref: RefObject<FieldElement> | (() => Element | RadioNodeList | FieldElement | null | undefined);
181
- onInput?: (event: Event) => void;
182
- onFocus?: (event: FocusEvent) => void;
183
- onBlur?: (event: FocusEvent) => void;
184
- onReset?: (event: Event) => void;
185
- }): InputControl;
186
- export declare const FORM_ERROR_ELEMENT_NAME = "__form__";
187
- /**
188
- * Validate the form with the Constraint Validation API
189
- * @see https://conform.guide/api/react#validateconstraint
190
- */
191
- export declare function validateConstraint(options: {
192
- form: HTMLFormElement;
193
- formData?: FormData;
194
- constraint?: Record<Lowercase<string>, (value: string, context: {
195
- formData: FormData;
196
- attributeValue: string;
197
- }) => boolean>;
198
- formatMessages?: ({ name, validity, constraint, defaultErrors, }: {
199
- name: string;
200
- validity: ValidityState;
201
- constraint: Record<string, boolean>;
202
- defaultErrors: string[];
203
- }) => string[];
204
- }): Submission;
205
- export declare function getUniqueKey(): string;
206
- export declare function reportSubmission(form: HTMLFormElement, submission: SubmissionResult): void;
207
- export declare function getScope(intent: ReturnType<typeof parseIntent>): string | null;
208
- export {};
27
+ export declare function useFormMetadata<Schema extends Record<string, any>>(options: {
28
+ formId: string;
29
+ context?: Form<Schema>;
30
+ defaultNoValidate?: boolean;
31
+ }): FormMetadata<Schema>;
32
+ export type FieldsetMetadata<Schema> = Schema extends Array<any> ? {
33
+ [Key in keyof Schema]: FieldMetadata<Schema[Key]>;
34
+ } : Schema extends {
35
+ [key in string]?: any;
36
+ } ? {
37
+ [Key in UnionKeyof<Schema>]: FieldMetadata<UnionKeyType<Schema, Key>>;
38
+ } : Record<string | number, FieldMetadata<any>>;
39
+ export declare function useFieldset<Schema>(options: {
40
+ formId: string;
41
+ name?: FieldName<Schema>;
42
+ context?: Form;
43
+ }): Pretty<FieldsetMetadata<Schema>>;
44
+ export type Item<List> = List extends Array<infer Item> ? Item : any;
45
+ export declare function useFieldList<Schema>(options: {
46
+ formId: string;
47
+ name: FieldName<Schema>;
48
+ context?: Form;
49
+ }): Array<FieldMetadata<Item<Schema>>>;
50
+ export declare function useField<Schema>(options: {
51
+ formId: string;
52
+ name: FieldName<Schema>;
53
+ context?: Form;
54
+ }): FieldMetadata<Schema>;