@conform-to/react 0.7.0-pre.2 → 0.7.0-pre.3

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/README.md CHANGED
@@ -53,7 +53,7 @@ function LoginForm() {
53
53
  * Define when conform should revalidate again.
54
54
  * Support "onSubmit", "onChange", "onBlur".
55
55
  *
56
- * Default to `onInput`.
56
+ * Default based on `shouldValidate`
57
57
  */
58
58
  shouldRevalidate: 'onInput',
59
59
 
@@ -159,7 +159,7 @@ function LoginForm() {
159
159
  This hook enables you to work with [nested object](/docs/configuration.md#nested-object) by monitoring the state of each nested field and prepraing the config required.
160
160
 
161
161
  ```tsx
162
- import { useForm, useFieldset, conform } from '@conform-to/react';
162
+ import { useForm, useFieldset } from '@conform-to/react';
163
163
 
164
164
  interface Address {
165
165
  street: string;
@@ -179,13 +179,13 @@ function Example() {
179
179
  <form {...form.props}>
180
180
  <fieldset>
181
181
  <legned>Address</legend>
182
- <input {...conform.input(street)} />
182
+ <input name={street.name} />
183
183
  <div>{street.error}</div>
184
- <input {...conform.input(zipcode)} />
184
+ <input name={zipcode.name} />
185
185
  <div>{zipcode.error}</div>
186
- <input {...conform.input(city)} />
186
+ <input name={city.name} />
187
187
  <div>{city.error}</div>
188
- <input {...conform.input(country)} />
188
+ <input name={country.name} />
189
189
  <div>{country.error}</div>
190
190
  </fieldset>
191
191
  <button>Submit</button>
@@ -259,7 +259,7 @@ function Example() {
259
259
  {itemsList.map((item, index) => (
260
260
  <div key={item.key}>
261
261
  {/* Setup an input per item */}
262
- <input {...conform.input(item)} />
262
+ <input name={item.name} />
263
263
 
264
264
  {/* Error of each item */}
265
265
  <span>{item.error}</span>
@@ -280,7 +280,7 @@ function Example() {
280
280
 
281
281
  ### useInputEvent
282
282
 
283
- It returns a ref object and a set of helpers that dispatch corresponding dom event.
283
+ It returns a set of helpers that dispatch corresponding dom event.
284
284
 
285
285
  ```tsx
286
286
  import { useForm, useInputEvent } from '@conform-to/react';
@@ -290,25 +290,28 @@ import { useState, useRef } from 'react';
290
290
  function MuiForm() {
291
291
  const [form, { category }] = useForm();
292
292
  const [value, setValue] = useState(category.defaultValue ?? '');
293
- const [ref, control] = useInputEvent({
293
+ const baseInputRef = useRef<HTMLInputElement>(null);
294
+ const customInputRef = useRef<HTMLInputElement>(null);
295
+ const control = useInputEvent({
296
+ ref: baseInputRef,
297
+ // Reset the state on form reset
294
298
  onReset: () => setValue(category.defaultValue ?? ''),
295
299
  });
296
- const inputRef = useRef<HTMLInputElement>(null);
297
300
 
298
301
  return (
299
302
  <form {...form.props}>
300
- {/* Render a shadow input somewhere */}
303
+ {/* Render a base input somewhere */}
301
304
  <input
302
- ref={ref}
305
+ ref={baseInputRef}
303
306
  {...conform.input(category, { hidden: true })}
304
307
  onChange={(e) => setValue(e.target.value)}
305
- onFocus={() => inputRef.current?.focus()}
308
+ onFocus={() => customInputRef.current?.focus()}
306
309
  />
307
310
 
308
311
  {/* MUI Select is a controlled component */}
309
312
  <TextField
310
313
  label="Category"
311
- inputRef={inputRef}
314
+ inputRef={customInputRef}
312
315
  value={value}
313
316
  onChange={control.change}
314
317
  onBlur={control.blur}
@@ -328,17 +331,16 @@ function MuiForm() {
328
331
 
329
332
  ### conform
330
333
 
331
- It provides several helpers to remove the boilerplate when configuring a form control and derives attributes for [accessibility](/docs/accessibility.md#configuration) concerns and helps [focus management](/docs/focus-management.md#focusing-before-javascript-is-loaded).
334
+ It provides several helpers to:
332
335
 
333
- You can also create a wrapper on top if you need to integrate with custom input component.
334
-
335
- Before:
336
+ - Minimize the boilerplate when configuring a form control
337
+ - Derive aria attributes for [accessibility](/docs/accessibility.md#configuration)
338
+ - Helps [focus management](/docs/focus-management.md#focusing-before-javascript-is-loaded)
336
339
 
337
340
  ```tsx
338
- import { useForm } from '@conform-to/react';
339
-
341
+ // Before
340
342
  function Example() {
341
- const [form, { title, description, category }] = useForm();
343
+ const [form, { title }] = useForm();
342
344
 
343
345
  return (
344
346
  <form {...form.props}>
@@ -347,6 +349,7 @@ function Example() {
347
349
  name={title.name}
348
350
  form={title.form}
349
351
  defaultValue={title.defaultValue}
352
+ autoFocus={title.initialError ? true : undefined}
350
353
  requried={title.required}
351
354
  minLength={title.minLength}
352
355
  maxLength={title.maxLength}
@@ -354,42 +357,25 @@ function Example() {
354
357
  max={title.max}
355
358
  multiple={title.multiple}
356
359
  pattern={title.pattern}
360
+ aria-invalid={title.error ? true : undefined}
361
+ aria-describedby={title.error ? `${title.name}-error` : undefined}
357
362
  />
358
- <textarea
359
- name={description.name}
360
- form={description.form}
361
- defaultValue={description.defaultValue}
362
- requried={description.required}
363
- minLength={description.minLength}
364
- maxLength={description.maxLength}
365
- />
366
- <select
367
- name={category.name}
368
- form={category.form}
369
- defaultValue={category.defaultValue}
370
- requried={category.required}
371
- multiple={category.multiple}
372
- >
373
- {/* ... */}
374
- </select>
375
363
  </form>
376
364
  );
377
365
  }
378
- ```
379
-
380
- After:
381
-
382
- ```tsx
383
- import { useForm, conform } from '@conform-to/react';
384
366
 
367
+ // After:
385
368
  function Example() {
386
369
  const [form, { title, description, category }] = useForm();
387
370
 
388
371
  return (
389
372
  <form {...form.props}>
390
- <input {...conform.input(title, { type: 'text' })} />
391
- <textarea {...conform.textarea(description)} />
392
- <select {...conform.select(category)}>{/* ... */}</select>
373
+ <input
374
+ {...conform.input(title, {
375
+ type: 'text',
376
+ ariaAttributes: true,
377
+ })}
378
+ />
393
379
  </form>
394
380
  );
395
381
  }
@@ -434,6 +420,8 @@ const submission = parse(formData, {
434
420
 
435
421
  ### validateConstraint
436
422
 
423
+ > This is a client only API
424
+
437
425
  This enable Constraint Validation with ability to enable custom constraint using data-attribute and customizing error messages. By default, the error message would be the attribute that triggered the error (e.g. `required` / `type` / 'minLength' etc).
438
426
 
439
427
  ```tsx
package/helpers.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { INTENT } from '@conform-to/dom';
2
- import { type FieldConfig, type Primitive, VALIDATION_UNDEFINED, VALIDATION_SKIPPED } from './hooks.js';
1
+ import { INTENT, VALIDATION_UNDEFINED, VALIDATION_SKIPPED } from '@conform-to/dom';
2
+ import type { FieldConfig, Primitive } from './hooks.js';
3
3
  import type { CSSProperties, HTMLInputTypeAttribute } from 'react';
4
4
  interface FormControlProps {
5
5
  id?: string;
@@ -36,6 +36,10 @@ interface TextareaProps extends FormControlProps {
36
36
  defaultValue?: string;
37
37
  }
38
38
  type BaseOptions = {
39
+ ariaAttributes?: false;
40
+ hidden?: boolean;
41
+ } | {
42
+ ariaAttributes: true;
39
43
  description?: boolean;
40
44
  hidden?: boolean;
41
45
  };
@@ -46,6 +50,11 @@ type InputOptions = BaseOptions & ({
46
50
  type?: Exclude<HTMLInputTypeAttribute, 'button' | 'submit' | 'hidden'>;
47
51
  value?: never;
48
52
  });
53
+ export declare const hiddenProps: {
54
+ hiddenStyle: CSSProperties;
55
+ tabIndex: number;
56
+ 'aria-hidden': boolean;
57
+ };
49
58
  export declare function input<Schema extends Primitive | unknown>(config: FieldConfig<Schema>, options?: InputOptions): InputProps<Schema>;
50
59
  export declare function input<Schema extends File | File[]>(config: FieldConfig<Schema>, options: InputOptions & {
51
60
  type: 'file';
package/helpers.js CHANGED
@@ -4,25 +4,8 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
6
6
  var dom = require('@conform-to/dom');
7
- var hooks = require('./hooks.js');
8
7
 
9
- /**
10
- * Style to make the input element visually hidden
11
- * Based on the `sr-only` class from tailwindcss
12
- */
13
- var hiddenStyle = {
14
- position: 'absolute',
15
- width: '1px',
16
- height: '1px',
17
- padding: 0,
18
- margin: '-1px',
19
- overflow: 'hidden',
20
- clip: 'rect(0,0,0,0)',
21
- whiteSpace: 'nowrap',
22
- border: 0
23
- };
24
8
  function getFormControlProps(config, options) {
25
- var _config$error;
26
9
  var props = {
27
10
  id: config.id,
28
11
  name: config.name,
@@ -32,23 +15,40 @@ function getFormControlProps(config, options) {
32
15
  if (config.id) {
33
16
  props.id = config.id;
34
17
  }
35
- if (config.descriptionId && options !== null && options !== void 0 && options.description) {
36
- props['aria-describedby'] = config.descriptionId;
37
- }
38
- if (config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length) {
39
- props['aria-invalid'] = true;
40
- props['aria-describedby'] = config.descriptionId && options !== null && options !== void 0 && options.description ? "".concat(config.errorId, " ").concat(config.descriptionId) : config.errorId;
18
+ if (options !== null && options !== void 0 && options.ariaAttributes) {
19
+ var _config$error;
20
+ if (config.descriptionId && options !== null && options !== void 0 && options.description) {
21
+ props['aria-describedby'] = config.descriptionId;
22
+ }
23
+ if (config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length) {
24
+ props['aria-invalid'] = true;
25
+ props['aria-describedby'] = config.descriptionId && options !== null && options !== void 0 && options.description ? "".concat(config.errorId, " ").concat(config.descriptionId) : config.errorId;
26
+ }
41
27
  }
42
28
  if (config.initialError && Object.entries(config.initialError).length > 0) {
43
29
  props.autoFocus = true;
44
30
  }
45
- if (options !== null && options !== void 0 && options.hidden) {
46
- props.style = hiddenStyle;
47
- props.tabIndex = -1;
48
- props['aria-hidden'] = true;
49
- }
50
- return props;
31
+ return options !== null && options !== void 0 && options.hidden ? _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, props), hiddenProps) : props;
51
32
  }
33
+ var hiddenProps = {
34
+ /**
35
+ * Style to make the input element visually hidden
36
+ * Based on the `sr-only` class from tailwindcss
37
+ */
38
+ hiddenStyle: {
39
+ position: 'absolute',
40
+ width: '1px',
41
+ height: '1px',
42
+ padding: 0,
43
+ margin: '-1px',
44
+ overflow: 'hidden',
45
+ clip: 'rect(0,0,0,0)',
46
+ whiteSpace: 'nowrap',
47
+ border: 0
48
+ },
49
+ tabIndex: -1,
50
+ 'aria-hidden': true
51
+ };
52
52
  function input(config) {
53
53
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
54
54
  var props = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, getFormControlProps(config, options)), {}, {
@@ -90,8 +90,15 @@ Object.defineProperty(exports, 'INTENT', {
90
90
  enumerable: true,
91
91
  get: function () { return dom.INTENT; }
92
92
  });
93
- exports.VALIDATION_SKIPPED = hooks.VALIDATION_SKIPPED;
94
- exports.VALIDATION_UNDEFINED = hooks.VALIDATION_UNDEFINED;
93
+ Object.defineProperty(exports, 'VALIDATION_SKIPPED', {
94
+ enumerable: true,
95
+ get: function () { return dom.VALIDATION_SKIPPED; }
96
+ });
97
+ Object.defineProperty(exports, 'VALIDATION_UNDEFINED', {
98
+ enumerable: true,
99
+ get: function () { return dom.VALIDATION_UNDEFINED; }
100
+ });
101
+ exports.hiddenProps = hiddenProps;
95
102
  exports.input = input;
96
103
  exports.select = select;
97
104
  exports.textarea = textarea;
package/helpers.mjs CHANGED
@@ -1,24 +1,7 @@
1
1
  import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
2
- export { INTENT } from '@conform-to/dom';
3
- export { VALIDATION_SKIPPED, VALIDATION_UNDEFINED } from './hooks.mjs';
2
+ export { INTENT, VALIDATION_SKIPPED, VALIDATION_UNDEFINED } from '@conform-to/dom';
4
3
 
5
- /**
6
- * Style to make the input element visually hidden
7
- * Based on the `sr-only` class from tailwindcss
8
- */
9
- var hiddenStyle = {
10
- position: 'absolute',
11
- width: '1px',
12
- height: '1px',
13
- padding: 0,
14
- margin: '-1px',
15
- overflow: 'hidden',
16
- clip: 'rect(0,0,0,0)',
17
- whiteSpace: 'nowrap',
18
- border: 0
19
- };
20
4
  function getFormControlProps(config, options) {
21
- var _config$error;
22
5
  var props = {
23
6
  id: config.id,
24
7
  name: config.name,
@@ -28,23 +11,40 @@ function getFormControlProps(config, options) {
28
11
  if (config.id) {
29
12
  props.id = config.id;
30
13
  }
31
- if (config.descriptionId && options !== null && options !== void 0 && options.description) {
32
- props['aria-describedby'] = config.descriptionId;
33
- }
34
- if (config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length) {
35
- props['aria-invalid'] = true;
36
- props['aria-describedby'] = config.descriptionId && options !== null && options !== void 0 && options.description ? "".concat(config.errorId, " ").concat(config.descriptionId) : config.errorId;
14
+ if (options !== null && options !== void 0 && options.ariaAttributes) {
15
+ var _config$error;
16
+ if (config.descriptionId && options !== null && options !== void 0 && options.description) {
17
+ props['aria-describedby'] = config.descriptionId;
18
+ }
19
+ if (config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length) {
20
+ props['aria-invalid'] = true;
21
+ props['aria-describedby'] = config.descriptionId && options !== null && options !== void 0 && options.description ? "".concat(config.errorId, " ").concat(config.descriptionId) : config.errorId;
22
+ }
37
23
  }
38
24
  if (config.initialError && Object.entries(config.initialError).length > 0) {
39
25
  props.autoFocus = true;
40
26
  }
41
- if (options !== null && options !== void 0 && options.hidden) {
42
- props.style = hiddenStyle;
43
- props.tabIndex = -1;
44
- props['aria-hidden'] = true;
45
- }
46
- return props;
27
+ return options !== null && options !== void 0 && options.hidden ? _objectSpread2(_objectSpread2({}, props), hiddenProps) : props;
47
28
  }
29
+ var hiddenProps = {
30
+ /**
31
+ * Style to make the input element visually hidden
32
+ * Based on the `sr-only` class from tailwindcss
33
+ */
34
+ hiddenStyle: {
35
+ position: 'absolute',
36
+ width: '1px',
37
+ height: '1px',
38
+ padding: 0,
39
+ margin: '-1px',
40
+ overflow: 'hidden',
41
+ clip: 'rect(0,0,0,0)',
42
+ whiteSpace: 'nowrap',
43
+ border: 0
44
+ },
45
+ tabIndex: -1,
46
+ 'aria-hidden': true
47
+ };
48
48
  function input(config) {
49
49
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
50
50
  var props = _objectSpread2(_objectSpread2({}, getFormControlProps(config, options)), {}, {
@@ -82,4 +82,4 @@ function textarea(config, options) {
82
82
  return props;
83
83
  }
84
84
 
85
- export { input, select, textarea };
85
+ export { hiddenProps, input, select, textarea };
package/hooks.d.ts CHANGED
@@ -31,22 +31,18 @@ export interface FormConfig<Output extends Record<string, any>, Input extends Re
31
31
  * A form ref object. Conform will fallback to its own ref object if it is not provided.
32
32
  */
33
33
  ref?: RefObject<HTMLFormElement>;
34
- /**
35
- * @deprecated Use `shouldValidate` and `shouldRevalidate` instead.
36
- */
37
- initialReport?: 'onSubmit' | 'onChange' | 'onBlur';
38
34
  /**
39
35
  * Define when conform should start validation.
40
36
  * Support "onSubmit", "onChange", "onBlur".
41
37
  *
42
- * Default to `onSubmit`.
38
+ * @default "onSubmit"
43
39
  */
44
40
  shouldValidate?: 'onSubmit' | 'onBlur' | 'onInput';
45
41
  /**
46
42
  * Define when conform should revalidate again.
47
43
  * Support "onSubmit", "onChange", "onBlur".
48
44
  *
49
- * Default to `onInput`.
45
+ * @default shouldValidate, or "onSubmit" if shouldValidate is not provided.
50
46
  */
51
47
  shouldRevalidate?: 'onSubmit' | 'onBlur' | 'onInput';
52
48
  /**
@@ -56,7 +52,7 @@ export interface FormConfig<Output extends Record<string, any>, Input extends Re
56
52
  /**
57
53
  * An object describing the result of the last submission
58
54
  */
59
- lastSubmission?: Submission;
55
+ lastSubmission?: Submission | null;
60
56
  /**
61
57
  * An object describing the constraint of each field
62
58
  */
@@ -166,26 +162,23 @@ interface InputControl {
166
162
  target: {
167
163
  value: string;
168
164
  };
169
- } | string) => void;
165
+ } | string | boolean) => void;
170
166
  focus: () => void;
171
167
  blur: () => void;
172
168
  }
169
+ export declare function useEventListeners(type: string, ref: RefObject<FieldElement>): void;
173
170
  /**
174
171
  * Returns a ref object and a set of helpers that dispatch corresponding dom event.
175
172
  *
176
173
  * @see https://conform.guide/api/react#useinputevent
177
174
  */
178
- export declare function useInputEvent<RefShape extends FieldElement = HTMLInputElement>(options?: {
179
- onSubmit?: (event: SubmitEvent) => void;
180
- onReset?: (event: Event) => void;
181
- }): [RefObject<RefShape>, InputControl];
182
- export declare function useInputEvent<RefShape extends Exclude<any, FieldElement>>(options: {
183
- getElement: (ref: RefShape | null) => FieldElement | null | undefined;
184
- onSubmit?: (event: SubmitEvent) => void;
175
+ export declare function useInputEvent(options: {
176
+ ref: RefObject<FieldElement> | (() => Element | RadioNodeList | FieldElement | null | undefined);
177
+ onInput?: (event: Event) => void;
178
+ onFocus?: (event: FocusEvent) => void;
179
+ onBlur?: (event: FocusEvent) => void;
185
180
  onReset?: (event: Event) => void;
186
- }): [RefObject<RefShape>, InputControl];
187
- export declare const VALIDATION_UNDEFINED = "__undefined__";
188
- export declare const VALIDATION_SKIPPED = "__skipped__";
181
+ }): InputControl;
189
182
  export declare const FORM_ERROR_ELEMENT_NAME = "__form__";
190
183
  /**
191
184
  * Validate the form with the Constraint Validation API