@conform-to/react 1.0.1 → 1.0.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 CHANGED
@@ -8,7 +8,7 @@
8
8
  ╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
9
9
 
10
10
 
11
- Version 1.0.1 / License MIT / Copyright (c) 2024 Edmund Hung
11
+ Version 1.0.3 / License MIT / Copyright (c) 2024 Edmund Hung
12
12
 
13
13
  A type-safe form validation library utilizing web fundamentals to progressively enhance HTML Forms with full support for server frameworks like Remix and Next.js.
14
14
 
package/context.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type Constraint, type FormId, type FieldName, type FormContext as BaseFormContext, type FormValue, type FormState, type Intent, type SubscriptionScope, type SubscriptionSubject, type UnionKeyof, type UnionKeyType, type FormOptions as BaseFormOptions } from '@conform-to/dom';
1
+ import { type Constraint, type Combine, type FormId, type FieldName, type FormContext as BaseFormContext, type FormValue, type FormState, type Intent, type SubscriptionScope, type SubscriptionSubject, type FormOptions as BaseFormOptions } from '@conform-to/dom';
2
2
  import { type FormEvent, type ReactElement, type ReactNode, type MutableRefObject } from 'react';
3
3
  export type Pretty<T> = {
4
4
  [K in keyof T]: T[K];
@@ -21,19 +21,22 @@ export type FormMetadata<Schema extends Record<string, unknown> = Record<string,
21
21
  id: FormId<Schema, FormError>;
22
22
  context: Wrapped<FormContext<Schema, FormError>>;
23
23
  status?: 'success' | 'error';
24
- getFieldset: () => {
25
- [Key in UnionKeyof<Schema>]: FieldMetadata<UnionKeyType<Schema, Key>, Schema, FormError>;
26
- };
24
+ getFieldset: () => Required<{
25
+ [Key in keyof Combine<Schema>]: FieldMetadata<Combine<Schema>[Key], Schema, FormError>;
26
+ }>;
27
27
  onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
28
28
  noValidate: boolean;
29
29
  };
30
+ type SubfieldMetadata<Schema, FormSchema extends Record<string, any>, FormError> = [Schema] extends [Primitive] ? {} : [Schema] extends [Array<infer Item> | null | undefined] ? {
31
+ getFieldList: () => Array<FieldMetadata<Item, FormSchema, FormError>>;
32
+ } : [Schema] extends [Record<string, any> | null | undefined] ? {
33
+ getFieldset: () => Required<{
34
+ [Key in keyof Combine<Schema>]: FieldMetadata<Combine<Schema>[Key], FormSchema, FormError>;
35
+ }>;
36
+ } : {};
30
37
  export type FieldMetadata<Schema = unknown, FormSchema extends Record<string, any> = Record<string, unknown>, FormError = string[]> = Metadata<Schema, FormSchema, FormError> & Constraint & {
31
38
  formId: FormId<FormSchema, FormError>;
32
- getFieldset: unknown extends Schema ? () => unknown : Schema extends Primitive | Array<any> ? never : () => {
33
- [Key in UnionKeyof<Schema>]: FieldMetadata<UnionKeyType<Schema, Key>, FormSchema, FormError>;
34
- };
35
- getFieldList: unknown extends Schema ? () => unknown : Schema extends Array<infer Item> ? () => Array<FieldMetadata<Item, FormSchema, FormError>> : never;
36
- };
39
+ } & SubfieldMetadata<Schema, FormSchema, FormError>;
37
40
  export declare const Form: import("react").Context<FormContext<any, string[], any>[]>;
38
41
  declare const wrappedSymbol: unique symbol;
39
42
  export type Wrapped<Type> = {
package/helpers.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import type { FormMetadata, FieldMetadata, Metadata, Pretty, Primitive } from './context';
2
+ import type { FormMetadata, FieldMetadata, Metadata, Pretty } from './context';
3
3
  type FormControlProps = {
4
4
  key: string | undefined;
5
5
  id: string;
@@ -125,7 +125,7 @@ export declare function getFormControlProps<Schema>(metadata: FieldMetadata<Sche
125
125
  * Depends on the provided options, it will also set `defaultChecked` / `checked` if it is a checkbox or radio button,
126
126
  * or otherwise `defaultValue` / `value`.
127
127
  *
128
- * @see https://conform.guide/api/react/get-input-props
128
+ * @see https://conform.guide/api/react/getInputProps
129
129
  * @example
130
130
  * ```tsx
131
131
  * // To setup an uncontrolled input
@@ -138,14 +138,14 @@ export declare function getFormControlProps<Schema>(metadata: FieldMetadata<Sche
138
138
  * <input {...getInputProps(metadata, { type: 'radio', value: false })} />
139
139
  * ```
140
140
  */
141
- export declare function getInputProps<Schema extends Primitive | File[]>(metadata: FieldMetadata<Schema, any, any>, options: InputOptions): InputProps;
141
+ export declare function getInputProps<Schema>(metadata: FieldMetadata<Schema, any, any>, options: InputOptions): InputProps;
142
142
  /**
143
143
  * Derives the properties of a select element based on the field metadata,
144
144
  * including common form control attributes like `key`, `id`, `name`, `form`, `autoFocus`, `aria-invalid`, `aria-describedby`
145
145
  * and constraint attributes such as `required` or `multiple`.
146
146
  * Depends on the provided options, it will also set `defaultValue` / `value`.
147
147
  *
148
- * @see https://conform.guide/api/react/get-select-props
148
+ * @see https://conform.guide/api/react/getSelectProps
149
149
  * @example
150
150
  * ```tsx
151
151
  * // To setup an uncontrolled select
@@ -154,14 +154,14 @@ export declare function getInputProps<Schema extends Primitive | File[]>(metadat
154
154
  * <select {...getSelectProps(metadata, { value: false })} />
155
155
  * ```
156
156
  */
157
- export declare function getSelectProps<Schema extends Exclude<Primitive, File> | Array<Exclude<Primitive, File>> | undefined>(metadata: FieldMetadata<Schema, any, any>, options?: SelectOptions): SelectProps;
157
+ export declare function getSelectProps<Schema>(metadata: FieldMetadata<Schema, any, any>, options?: SelectOptions): SelectProps;
158
158
  /**
159
159
  * Derives the properties of a textarea element based on the field metadata,
160
160
  * including common form control attributes like `key`, `id`, `name`, `form`, `autoFocus`, `aria-invalid`, `aria-describedby`
161
161
  * and constraint attributes such as `required`, `minLength` or `maxLength`.
162
162
  * Depends on the provided options, it will also set `defaultValue` / `value`.
163
163
  *
164
- * @see https://conform.guide/api/react/get-textarea-props
164
+ * @see https://conform.guide/api/react/getTextareaProps
165
165
  * @example
166
166
  * ```tsx
167
167
  * // To setup an uncontrolled textarea
@@ -170,12 +170,12 @@ export declare function getSelectProps<Schema extends Exclude<Primitive, File> |
170
170
  * <textarea {...getTextareaProps(metadata, { value: false })} />
171
171
  * ```
172
172
  */
173
- export declare function getTextareaProps<Schema extends Exclude<Primitive, File> | undefined>(metadata: FieldMetadata<Schema, any, any>, options?: TextareaOptions): TextareaProps;
173
+ export declare function getTextareaProps<Schema>(metadata: FieldMetadata<Schema, any, any>, options?: TextareaOptions): TextareaProps;
174
174
  /**
175
175
  * Derives the properties of a collection of checkboxes or radio buttons based on the field metadata,
176
176
  * including common form control attributes like `key`, `id`, `name`, `form`, `autoFocus`, `aria-invalid`, `aria-describedby` and `required`.
177
177
  *
178
- * @see https://conform.guide/api/react/get-textarea-props
178
+ * @see https://conform.guide/api/react/getTextareaProps
179
179
  * @example
180
180
  * ```tsx
181
181
  * <fieldset>
@@ -201,7 +201,7 @@ export declare function getCollectionProps<Schema extends Array<string | boolean
201
201
  */
202
202
  options: string[];
203
203
  /**
204
- * Decide whether defaultValue should be returned. Pass `false` if you want to mange the value yourself.
204
+ * Decide whether defaultValue should be returned. Pass `false` if you want to manage the value yourself.
205
205
  */
206
206
  value?: boolean;
207
207
  }>): Array<InputProps & Pick<Required<InputProps>, 'type' | 'value'>>;
package/helpers.js CHANGED
@@ -85,7 +85,7 @@ function getFormControlProps(metadata, options) {
85
85
  * Depends on the provided options, it will also set `defaultChecked` / `checked` if it is a checkbox or radio button,
86
86
  * or otherwise `defaultValue` / `value`.
87
87
  *
88
- * @see https://conform.guide/api/react/get-input-props
88
+ * @see https://conform.guide/api/react/getInputProps
89
89
  * @example
90
90
  * ```tsx
91
91
  * // To setup an uncontrolled input
@@ -126,7 +126,7 @@ function getInputProps(metadata, options) {
126
126
  * and constraint attributes such as `required` or `multiple`.
127
127
  * Depends on the provided options, it will also set `defaultValue` / `value`.
128
128
  *
129
- * @see https://conform.guide/api/react/get-select-props
129
+ * @see https://conform.guide/api/react/getSelectProps
130
130
  * @example
131
131
  * ```tsx
132
132
  * // To setup an uncontrolled select
@@ -141,7 +141,8 @@ function getSelectProps(metadata) {
141
141
  multiple: metadata.multiple
142
142
  });
143
143
  if (typeof options.value === 'undefined' || options.value) {
144
- props.defaultValue = Array.isArray(metadata.initialValue) ? metadata.initialValue.map(item => "".concat(item !== null && item !== void 0 ? item : '')) : metadata.initialValue;
144
+ var _metadata$initialValu;
145
+ props.defaultValue = Array.isArray(metadata.initialValue) ? metadata.initialValue.map(item => "".concat(item !== null && item !== void 0 ? item : '')) : (_metadata$initialValu = metadata.initialValue) === null || _metadata$initialValu === void 0 ? void 0 : _metadata$initialValu.toString();
145
146
  }
146
147
  return simplify(props);
147
148
  }
@@ -152,7 +153,7 @@ function getSelectProps(metadata) {
152
153
  * and constraint attributes such as `required`, `minLength` or `maxLength`.
153
154
  * Depends on the provided options, it will also set `defaultValue` / `value`.
154
155
  *
155
- * @see https://conform.guide/api/react/get-textarea-props
156
+ * @see https://conform.guide/api/react/getTextareaProps
156
157
  * @example
157
158
  * ```tsx
158
159
  * // To setup an uncontrolled textarea
@@ -168,7 +169,8 @@ function getTextareaProps(metadata) {
168
169
  maxLength: metadata.maxLength
169
170
  });
170
171
  if (typeof options.value === 'undefined' || options.value) {
171
- props.defaultValue = metadata.initialValue;
172
+ var _metadata$initialValu2;
173
+ props.defaultValue = (_metadata$initialValu2 = metadata.initialValue) === null || _metadata$initialValu2 === void 0 ? void 0 : _metadata$initialValu2.toString();
172
174
  }
173
175
  return simplify(props);
174
176
  }
@@ -177,7 +179,7 @@ function getTextareaProps(metadata) {
177
179
  * Derives the properties of a collection of checkboxes or radio buttons based on the field metadata,
178
180
  * including common form control attributes like `key`, `id`, `name`, `form`, `autoFocus`, `aria-invalid`, `aria-describedby` and `required`.
179
181
  *
180
- * @see https://conform.guide/api/react/get-textarea-props
182
+ * @see https://conform.guide/api/react/getTextareaProps
181
183
  * @example
182
184
  * ```tsx
183
185
  * <fieldset>
package/helpers.mjs CHANGED
@@ -81,7 +81,7 @@ function getFormControlProps(metadata, options) {
81
81
  * Depends on the provided options, it will also set `defaultChecked` / `checked` if it is a checkbox or radio button,
82
82
  * or otherwise `defaultValue` / `value`.
83
83
  *
84
- * @see https://conform.guide/api/react/get-input-props
84
+ * @see https://conform.guide/api/react/getInputProps
85
85
  * @example
86
86
  * ```tsx
87
87
  * // To setup an uncontrolled input
@@ -122,7 +122,7 @@ function getInputProps(metadata, options) {
122
122
  * and constraint attributes such as `required` or `multiple`.
123
123
  * Depends on the provided options, it will also set `defaultValue` / `value`.
124
124
  *
125
- * @see https://conform.guide/api/react/get-select-props
125
+ * @see https://conform.guide/api/react/getSelectProps
126
126
  * @example
127
127
  * ```tsx
128
128
  * // To setup an uncontrolled select
@@ -137,7 +137,8 @@ function getSelectProps(metadata) {
137
137
  multiple: metadata.multiple
138
138
  });
139
139
  if (typeof options.value === 'undefined' || options.value) {
140
- props.defaultValue = Array.isArray(metadata.initialValue) ? metadata.initialValue.map(item => "".concat(item !== null && item !== void 0 ? item : '')) : metadata.initialValue;
140
+ var _metadata$initialValu;
141
+ props.defaultValue = Array.isArray(metadata.initialValue) ? metadata.initialValue.map(item => "".concat(item !== null && item !== void 0 ? item : '')) : (_metadata$initialValu = metadata.initialValue) === null || _metadata$initialValu === void 0 ? void 0 : _metadata$initialValu.toString();
141
142
  }
142
143
  return simplify(props);
143
144
  }
@@ -148,7 +149,7 @@ function getSelectProps(metadata) {
148
149
  * and constraint attributes such as `required`, `minLength` or `maxLength`.
149
150
  * Depends on the provided options, it will also set `defaultValue` / `value`.
150
151
  *
151
- * @see https://conform.guide/api/react/get-textarea-props
152
+ * @see https://conform.guide/api/react/getTextareaProps
152
153
  * @example
153
154
  * ```tsx
154
155
  * // To setup an uncontrolled textarea
@@ -164,7 +165,8 @@ function getTextareaProps(metadata) {
164
165
  maxLength: metadata.maxLength
165
166
  });
166
167
  if (typeof options.value === 'undefined' || options.value) {
167
- props.defaultValue = metadata.initialValue;
168
+ var _metadata$initialValu2;
169
+ props.defaultValue = (_metadata$initialValu2 = metadata.initialValue) === null || _metadata$initialValu2 === void 0 ? void 0 : _metadata$initialValu2.toString();
168
170
  }
169
171
  return simplify(props);
170
172
  }
@@ -173,7 +175,7 @@ function getTextareaProps(metadata) {
173
175
  * Derives the properties of a collection of checkboxes or radio buttons based on the field metadata,
174
176
  * including common form control attributes like `key`, `id`, `name`, `form`, `autoFocus`, `aria-invalid`, `aria-describedby` and `required`.
175
177
  *
176
- * @see https://conform.guide/api/react/get-textarea-props
178
+ * @see https://conform.guide/api/react/getTextareaProps
177
179
  * @example
178
180
  * ```tsx
179
181
  * <fieldset>
package/hooks.d.ts CHANGED
@@ -8,7 +8,7 @@ import { type FormMetadata, type FieldMetadata, type Pretty, type FormOptions }
8
8
  export declare const useSafeLayoutEffect: typeof useEffect;
9
9
  export declare function useFormId<Schema extends Record<string, unknown>, FormError>(preferredId?: string): FormId<Schema, FormError>;
10
10
  export declare function useNoValidate(defaultNoValidate?: boolean): boolean;
11
- export declare function useForm<Schema extends Record<string, any>, FormValue = Schema, FormError = string[]>(options: Pretty<Omit<FormOptions<Schema, FormError, FormValue>, 'formId'> & {
11
+ export declare function useForm<Schema extends Record<string, any>, FormValue = Schema, FormError = string[]>(options: Pretty<Omit<FormOptions<Schema, FormError, FormValue>, 'formId' | 'onSchedule'> & {
12
12
  /**
13
13
  * The form id. If not provided, a random id will be generated.
14
14
  */
package/hooks.js CHANGED
@@ -4,6 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
6
6
  var react = require('react');
7
+ var reactDom = require('react-dom');
7
8
  var context = require('./context.js');
8
9
 
9
10
  var _excluded = ["id"];
@@ -37,6 +38,7 @@ function useForm(options) {
37
38
  formConfig = _rollupPluginBabelHelpers.objectWithoutProperties(options, _excluded);
38
39
  var formId = useFormId(id);
39
40
  var [context$1] = react.useState(() => context.createFormContext(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, formConfig), {}, {
41
+ onSchedule: callback => reactDom.flushSync(callback),
40
42
  formId
41
43
  })));
42
44
  useSafeLayoutEffect(() => {
package/hooks.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
2
2
  import { useState, useEffect, useLayoutEffect, useId } from 'react';
3
+ import { flushSync } from 'react-dom';
3
4
  import { createFormContext, useSubjectRef, useFormState, getFormMetadata, useFormContext, getFieldMetadata } from './context.mjs';
4
5
 
5
6
  var _excluded = ["id"];
@@ -33,6 +34,7 @@ function useForm(options) {
33
34
  formConfig = _objectWithoutProperties(options, _excluded);
34
35
  var formId = useFormId(id);
35
36
  var [context] = useState(() => createFormContext(_objectSpread2(_objectSpread2({}, formConfig), {}, {
37
+ onSchedule: callback => flushSync(callback),
36
38
  formId
37
39
  })));
38
40
  useSafeLayoutEffect(() => {
package/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { type Submission, type SubmissionResult, type DefaultValue, type Intent, type FormId, type FieldName, parse, } from '@conform-to/dom';
2
2
  export { type FieldMetadata, type FormMetadata, FormProvider, FormStateInput, } from './context';
3
3
  export { useForm, useFormMetadata, useField } from './hooks';
4
- export { useInputControl } from './integrations';
4
+ export { Control as unstable_Control, useControl as unstable_useControl, useInputControl, } from './integrations';
5
5
  export { getFormProps, getFieldsetProps, getInputProps, getSelectProps, getTextareaProps, getCollectionProps, } from './helpers';
package/index.js CHANGED
@@ -19,6 +19,8 @@ exports.FormStateInput = context.FormStateInput;
19
19
  exports.useField = hooks.useField;
20
20
  exports.useForm = hooks.useForm;
21
21
  exports.useFormMetadata = hooks.useFormMetadata;
22
+ exports.unstable_Control = integrations.Control;
23
+ exports.unstable_useControl = integrations.useControl;
22
24
  exports.useInputControl = integrations.useInputControl;
23
25
  exports.getCollectionProps = helpers.getCollectionProps;
24
26
  exports.getFieldsetProps = helpers.getFieldsetProps;
package/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  export { parse } from '@conform-to/dom';
2
2
  export { FormProvider, FormStateInput } from './context.mjs';
3
3
  export { useField, useForm, useFormMetadata } from './hooks.mjs';
4
- export { useInputControl } from './integrations.mjs';
4
+ export { Control as unstable_Control, useControl as unstable_useControl, useInputControl } from './integrations.mjs';
5
5
  export { getCollectionProps, getFieldsetProps, getFormProps, getInputProps, getSelectProps, getTextareaProps } from './helpers.mjs';
package/integrations.d.ts CHANGED
@@ -1,19 +1,45 @@
1
- import { type Key } from 'react';
2
- export type InputControl<Value> = {
3
- value: Value | undefined;
4
- change: (value: Value) => void;
5
- focus: () => void;
6
- blur: () => void;
7
- };
8
- export declare function getFormElement(formId: string): HTMLFormElement;
9
- export declare function getFieldElements(form: HTMLFormElement, name: string): Array<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>;
10
- export declare function getEventTarget(form: HTMLFormElement, name: string): HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | null;
1
+ import { type Key, type RefCallback } from 'react';
2
+ export declare function getFormElement(formId: string): HTMLFormElement | null;
3
+ export declare function getFieldElements(form: HTMLFormElement | null, name: string): Array<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>;
4
+ export declare function getEventTarget(form: HTMLFormElement | null, name: string, value?: string | string[]): HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | null;
11
5
  export declare function createDummySelect(form: HTMLFormElement, name: string, value?: string | string[] | undefined): HTMLSelectElement;
12
6
  export declare function isDummySelect(element: HTMLElement): element is HTMLSelectElement;
13
7
  export declare function updateFieldValue(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, value: string | string[]): void;
14
- export declare function useInputControl<Value>(metaOrOptions: {
8
+ export declare function useInputEvent(): {
9
+ change(value: string | string[]): void;
10
+ focus(): void;
11
+ blur(): void;
12
+ register: RefCallback<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | undefined>;
13
+ };
14
+ export declare function useInputValue<Value extends string | string[] | Array<string | undefined>>(options: {
15
+ key?: Key | null | undefined;
16
+ initialValue?: Value | undefined;
17
+ }): readonly [(Value extends string ? Value : string | string[]) | undefined, import("react").Dispatch<import("react").SetStateAction<(Value extends string ? Value : string | string[]) | undefined>>];
18
+ export declare function useControl<Value extends string | string[] | Array<string | undefined>>(meta: {
19
+ key?: Key | null | undefined;
20
+ initialValue?: Value | undefined;
21
+ }): {
22
+ register: RefCallback<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | undefined>;
23
+ value: (Value extends string ? Value : string | string[]) | undefined;
24
+ change: (value: Value extends string ? Value : string | string[]) => void;
25
+ focus: () => void;
26
+ blur: () => void;
27
+ };
28
+ export declare function useInputControl<Value extends string | string[] | Array<string | undefined>>(meta: {
15
29
  key?: Key | null | undefined;
16
30
  name: string;
17
31
  formId: string;
18
32
  initialValue?: Value | undefined;
19
- }): InputControl<Value extends string ? string : string | string[]>;
33
+ }): {
34
+ value: (Value extends string ? Value : string | string[]) | undefined;
35
+ change: import("react").Dispatch<import("react").SetStateAction<(Value extends string ? Value : string | string[]) | undefined>>;
36
+ focus: () => void;
37
+ blur: () => void;
38
+ };
39
+ export declare function Control<Value extends string | string[] | Array<string | undefined>>(props: {
40
+ meta: {
41
+ key?: Key | null | undefined;
42
+ initialValue?: Value | undefined;
43
+ };
44
+ render: (control: ReturnType<typeof useControl<Value>>) => React.ReactNode;
45
+ }): import("react").ReactNode;
package/integrations.js CHANGED
@@ -2,24 +2,28 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
6
5
  var react = require('react');
7
6
 
8
7
  function getFormElement(formId) {
9
- var element = document.forms.namedItem(formId);
10
- if (!element) {
11
- throw new Error('Form not found');
12
- }
13
- return element;
8
+ return document.forms.namedItem(formId);
14
9
  }
15
10
  function getFieldElements(form, name) {
16
- var field = form.elements.namedItem(name);
11
+ var field = form === null || form === void 0 ? void 0 : form.elements.namedItem(name);
17
12
  var elements = !field ? [] : field instanceof Element ? [field] : Array.from(field.values());
18
13
  return elements.filter(element => element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement);
19
14
  }
20
- function getEventTarget(form, name) {
15
+ function getEventTarget(form, name, value) {
21
16
  var _elements$;
22
17
  var elements = getFieldElements(form, name);
18
+ if (elements.length > 1) {
19
+ var options = typeof value === 'string' ? [value] : value;
20
+ for (var element of elements) {
21
+ if (typeof options !== 'undefined' && element instanceof HTMLInputElement && element.type === 'checkbox' && (element.checked ? options.includes(element.value) : !options.includes(element.value))) {
22
+ continue;
23
+ }
24
+ return element;
25
+ }
26
+ }
23
27
  return (_elements$ = elements[0]) !== null && _elements$ !== void 0 ? _elements$ : null;
24
28
  }
25
29
  function createDummySelect(form, name, value) {
@@ -52,7 +56,7 @@ function isDummySelect(element) {
52
56
  }
53
57
  function updateFieldValue(element, value) {
54
58
  if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {
55
- element.checked = element.value === value;
59
+ element.checked = Array.isArray(value) ? value.includes(element.value) : element.value === value;
56
60
  } else if (element instanceof HTMLSelectElement && element.multiple) {
57
61
  var selectedValue = Array.isArray(value) ? [...value] : [value];
58
62
  for (var option of element.options) {
@@ -100,44 +104,18 @@ function updateFieldValue(element, value) {
100
104
  }
101
105
  }
102
106
  }
103
- function useInputControl(metaOrOptions) {
104
- // If the initialValue is an array, it must be a string array without undefined values
105
- // as there is no way to skip an entry in a multiple select when they all share the same name
106
- var inputInitialValue = metaOrOptions.initialValue;
107
+ function useInputEvent() {
108
+ var ref = react.useRef(null);
107
109
  var eventDispatched = react.useRef({
108
110
  change: false,
109
111
  focus: false,
110
112
  blur: false
111
113
  });
112
- var [key, setKey] = react.useState(metaOrOptions.key);
113
- var [initialValue, setInitialValue] = react.useState(inputInitialValue);
114
- var [value, setValue] = react.useState(inputInitialValue);
115
- if (key !== metaOrOptions.key) {
116
- setValue(inputInitialValue);
117
- setInitialValue(inputInitialValue);
118
- setKey(metaOrOptions.key);
119
- }
120
- react.useEffect(() => {
121
- var form = getFormElement(metaOrOptions.formId);
122
- if (getEventTarget(form, metaOrOptions.name)) {
123
- return;
124
- }
125
- createDummySelect(form, metaOrOptions.name, initialValue);
126
- return () => {
127
- var elements = getFieldElements(form, metaOrOptions.name);
128
- for (var element of elements) {
129
- if (isDummySelect(element)) {
130
- element.remove();
131
- }
132
- }
133
- };
134
- }, [metaOrOptions.formId, metaOrOptions.name, initialValue]);
135
114
  react.useEffect(() => {
136
115
  var createEventListener = listener => {
137
116
  return event => {
138
- var _element$form;
139
- var element = event.target;
140
- if ((element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement) && element.name === metaOrOptions.name && ((_element$form = element.form) === null || _element$form === void 0 ? void 0 : _element$form.id) === metaOrOptions.formId) {
117
+ var element = ref.current;
118
+ if (element && event.target === element) {
141
119
  eventDispatched.current[listener] = true;
142
120
  }
143
121
  };
@@ -153,14 +131,13 @@ function useInputControl(metaOrOptions) {
153
131
  document.removeEventListener('focusin', focusHandler, true);
154
132
  document.removeEventListener('focusout', blurHandler, true);
155
133
  };
156
- }, [metaOrOptions.formId, metaOrOptions.name]);
157
- var handlers = react.useMemo(() => {
134
+ }, [ref]);
135
+ return react.useMemo(() => {
158
136
  return {
159
137
  change(value) {
160
138
  if (!eventDispatched.current.change) {
161
139
  eventDispatched.current.change = true;
162
- var form = getFormElement(metaOrOptions.formId);
163
- var element = getEventTarget(form, metaOrOptions.name);
140
+ var element = ref.current;
164
141
  if (element) {
165
142
  updateFieldValue(element, value);
166
143
 
@@ -174,14 +151,12 @@ function useInputControl(metaOrOptions) {
174
151
  }));
175
152
  }
176
153
  }
177
- setValue(value);
178
154
  eventDispatched.current.change = false;
179
155
  },
180
156
  focus() {
181
157
  if (!eventDispatched.current.focus) {
182
158
  eventDispatched.current.focus = true;
183
- var form = getFormElement(metaOrOptions.formId);
184
- var element = getEventTarget(form, metaOrOptions.name);
159
+ var element = ref.current;
185
160
  if (element) {
186
161
  element.dispatchEvent(new FocusEvent('focusin', {
187
162
  bubbles: true
@@ -194,8 +169,7 @@ function useInputControl(metaOrOptions) {
194
169
  blur() {
195
170
  if (!eventDispatched.current.blur) {
196
171
  eventDispatched.current.blur = true;
197
- var form = getFormElement(metaOrOptions.formId);
198
- var element = getEventTarget(form, metaOrOptions.name);
172
+ var element = ref.current;
199
173
  if (element) {
200
174
  element.dispatchEvent(new FocusEvent('focusout', {
201
175
  bubbles: true
@@ -204,18 +178,108 @@ function useInputControl(metaOrOptions) {
204
178
  }
205
179
  }
206
180
  eventDispatched.current.blur = false;
181
+ },
182
+ register(element) {
183
+ ref.current = element;
207
184
  }
208
185
  };
209
- }, [metaOrOptions.formId, metaOrOptions.name]);
210
- return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, handlers), {}, {
211
- value
212
- });
186
+ }, []);
187
+ }
188
+ function useInputValue(options) {
189
+ var initializeValue = () => {
190
+ var _options$initialValue;
191
+ if (typeof options.initialValue === 'string') {
192
+ // @ts-expect-error FIXME: To ensure that the type of value is also `string | undefined` if initialValue is not an array
193
+ return options.initialValue;
194
+ }
195
+
196
+ // @ts-expect-error Same as above
197
+ return (_options$initialValue = options.initialValue) === null || _options$initialValue === void 0 ? void 0 : _options$initialValue.map(value => value !== null && value !== void 0 ? value : '');
198
+ };
199
+ var [key, setKey] = react.useState(options.key);
200
+ var [value, setValue] = react.useState(initializeValue);
201
+ if (key !== options.key) {
202
+ setValue(initializeValue);
203
+ setKey(options.key);
204
+ }
205
+ return [value, setValue];
206
+ }
207
+ function useControl(meta) {
208
+ var [value, setValue] = useInputValue(meta);
209
+ var {
210
+ register,
211
+ change,
212
+ focus,
213
+ blur
214
+ } = useInputEvent();
215
+ var handleChange = value => {
216
+ setValue(value);
217
+ change(value);
218
+ };
219
+ return {
220
+ register,
221
+ value,
222
+ change: handleChange,
223
+ focus,
224
+ blur
225
+ };
226
+ }
227
+ function useInputControl(meta) {
228
+ var [value, setValue] = useInputValue(meta);
229
+ var initializedRef = react.useRef(false);
230
+ var {
231
+ register,
232
+ change,
233
+ focus,
234
+ blur
235
+ } = useInputEvent();
236
+ react.useEffect(() => {
237
+ var form = getFormElement(meta.formId);
238
+ if (!form) {
239
+ // eslint-disable-next-line no-console
240
+ console.warn("useInputControl is unable to find form#".concat(meta.formId, " and identify if a dummy input is required"));
241
+ return;
242
+ }
243
+ var element = getEventTarget(form, meta.name);
244
+ if (!element && typeof value !== 'undefined' && (!Array.isArray(value) || value.length > 0)) {
245
+ element = createDummySelect(form, meta.name, value);
246
+ }
247
+ register(element);
248
+ if (!initializedRef.current) {
249
+ initializedRef.current = true;
250
+ } else {
251
+ change(value !== null && value !== void 0 ? value : '');
252
+ }
253
+ return () => {
254
+ register(null);
255
+ var elements = getFieldElements(form, meta.name);
256
+ for (var _element of elements) {
257
+ if (isDummySelect(_element)) {
258
+ _element.remove();
259
+ }
260
+ }
261
+ };
262
+ }, [meta.formId, meta.name, value, change, register]);
263
+ return {
264
+ value,
265
+ change: setValue,
266
+ focus,
267
+ blur
268
+ };
269
+ }
270
+ function Control(props) {
271
+ var control = useControl(props.meta);
272
+ return props.render(control);
213
273
  }
214
274
 
275
+ exports.Control = Control;
215
276
  exports.createDummySelect = createDummySelect;
216
277
  exports.getEventTarget = getEventTarget;
217
278
  exports.getFieldElements = getFieldElements;
218
279
  exports.getFormElement = getFormElement;
219
280
  exports.isDummySelect = isDummySelect;
220
281
  exports.updateFieldValue = updateFieldValue;
282
+ exports.useControl = useControl;
221
283
  exports.useInputControl = useInputControl;
284
+ exports.useInputEvent = useInputEvent;
285
+ exports.useInputValue = useInputValue;
package/integrations.mjs CHANGED
@@ -1,21 +1,25 @@
1
- import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
2
- import { useRef, useState, useEffect, useMemo } from 'react';
1
+ import { useRef, useEffect, useMemo, useState } from 'react';
3
2
 
4
3
  function getFormElement(formId) {
5
- var element = document.forms.namedItem(formId);
6
- if (!element) {
7
- throw new Error('Form not found');
8
- }
9
- return element;
4
+ return document.forms.namedItem(formId);
10
5
  }
11
6
  function getFieldElements(form, name) {
12
- var field = form.elements.namedItem(name);
7
+ var field = form === null || form === void 0 ? void 0 : form.elements.namedItem(name);
13
8
  var elements = !field ? [] : field instanceof Element ? [field] : Array.from(field.values());
14
9
  return elements.filter(element => element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement);
15
10
  }
16
- function getEventTarget(form, name) {
11
+ function getEventTarget(form, name, value) {
17
12
  var _elements$;
18
13
  var elements = getFieldElements(form, name);
14
+ if (elements.length > 1) {
15
+ var options = typeof value === 'string' ? [value] : value;
16
+ for (var element of elements) {
17
+ if (typeof options !== 'undefined' && element instanceof HTMLInputElement && element.type === 'checkbox' && (element.checked ? options.includes(element.value) : !options.includes(element.value))) {
18
+ continue;
19
+ }
20
+ return element;
21
+ }
22
+ }
19
23
  return (_elements$ = elements[0]) !== null && _elements$ !== void 0 ? _elements$ : null;
20
24
  }
21
25
  function createDummySelect(form, name, value) {
@@ -48,7 +52,7 @@ function isDummySelect(element) {
48
52
  }
49
53
  function updateFieldValue(element, value) {
50
54
  if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {
51
- element.checked = element.value === value;
55
+ element.checked = Array.isArray(value) ? value.includes(element.value) : element.value === value;
52
56
  } else if (element instanceof HTMLSelectElement && element.multiple) {
53
57
  var selectedValue = Array.isArray(value) ? [...value] : [value];
54
58
  for (var option of element.options) {
@@ -96,44 +100,18 @@ function updateFieldValue(element, value) {
96
100
  }
97
101
  }
98
102
  }
99
- function useInputControl(metaOrOptions) {
100
- // If the initialValue is an array, it must be a string array without undefined values
101
- // as there is no way to skip an entry in a multiple select when they all share the same name
102
- var inputInitialValue = metaOrOptions.initialValue;
103
+ function useInputEvent() {
104
+ var ref = useRef(null);
103
105
  var eventDispatched = useRef({
104
106
  change: false,
105
107
  focus: false,
106
108
  blur: false
107
109
  });
108
- var [key, setKey] = useState(metaOrOptions.key);
109
- var [initialValue, setInitialValue] = useState(inputInitialValue);
110
- var [value, setValue] = useState(inputInitialValue);
111
- if (key !== metaOrOptions.key) {
112
- setValue(inputInitialValue);
113
- setInitialValue(inputInitialValue);
114
- setKey(metaOrOptions.key);
115
- }
116
- useEffect(() => {
117
- var form = getFormElement(metaOrOptions.formId);
118
- if (getEventTarget(form, metaOrOptions.name)) {
119
- return;
120
- }
121
- createDummySelect(form, metaOrOptions.name, initialValue);
122
- return () => {
123
- var elements = getFieldElements(form, metaOrOptions.name);
124
- for (var element of elements) {
125
- if (isDummySelect(element)) {
126
- element.remove();
127
- }
128
- }
129
- };
130
- }, [metaOrOptions.formId, metaOrOptions.name, initialValue]);
131
110
  useEffect(() => {
132
111
  var createEventListener = listener => {
133
112
  return event => {
134
- var _element$form;
135
- var element = event.target;
136
- if ((element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement) && element.name === metaOrOptions.name && ((_element$form = element.form) === null || _element$form === void 0 ? void 0 : _element$form.id) === metaOrOptions.formId) {
113
+ var element = ref.current;
114
+ if (element && event.target === element) {
137
115
  eventDispatched.current[listener] = true;
138
116
  }
139
117
  };
@@ -149,14 +127,13 @@ function useInputControl(metaOrOptions) {
149
127
  document.removeEventListener('focusin', focusHandler, true);
150
128
  document.removeEventListener('focusout', blurHandler, true);
151
129
  };
152
- }, [metaOrOptions.formId, metaOrOptions.name]);
153
- var handlers = useMemo(() => {
130
+ }, [ref]);
131
+ return useMemo(() => {
154
132
  return {
155
133
  change(value) {
156
134
  if (!eventDispatched.current.change) {
157
135
  eventDispatched.current.change = true;
158
- var form = getFormElement(metaOrOptions.formId);
159
- var element = getEventTarget(form, metaOrOptions.name);
136
+ var element = ref.current;
160
137
  if (element) {
161
138
  updateFieldValue(element, value);
162
139
 
@@ -170,14 +147,12 @@ function useInputControl(metaOrOptions) {
170
147
  }));
171
148
  }
172
149
  }
173
- setValue(value);
174
150
  eventDispatched.current.change = false;
175
151
  },
176
152
  focus() {
177
153
  if (!eventDispatched.current.focus) {
178
154
  eventDispatched.current.focus = true;
179
- var form = getFormElement(metaOrOptions.formId);
180
- var element = getEventTarget(form, metaOrOptions.name);
155
+ var element = ref.current;
181
156
  if (element) {
182
157
  element.dispatchEvent(new FocusEvent('focusin', {
183
158
  bubbles: true
@@ -190,8 +165,7 @@ function useInputControl(metaOrOptions) {
190
165
  blur() {
191
166
  if (!eventDispatched.current.blur) {
192
167
  eventDispatched.current.blur = true;
193
- var form = getFormElement(metaOrOptions.formId);
194
- var element = getEventTarget(form, metaOrOptions.name);
168
+ var element = ref.current;
195
169
  if (element) {
196
170
  element.dispatchEvent(new FocusEvent('focusout', {
197
171
  bubbles: true
@@ -200,12 +174,98 @@ function useInputControl(metaOrOptions) {
200
174
  }
201
175
  }
202
176
  eventDispatched.current.blur = false;
177
+ },
178
+ register(element) {
179
+ ref.current = element;
203
180
  }
204
181
  };
205
- }, [metaOrOptions.formId, metaOrOptions.name]);
206
- return _objectSpread2(_objectSpread2({}, handlers), {}, {
207
- value
208
- });
182
+ }, []);
183
+ }
184
+ function useInputValue(options) {
185
+ var initializeValue = () => {
186
+ var _options$initialValue;
187
+ if (typeof options.initialValue === 'string') {
188
+ // @ts-expect-error FIXME: To ensure that the type of value is also `string | undefined` if initialValue is not an array
189
+ return options.initialValue;
190
+ }
191
+
192
+ // @ts-expect-error Same as above
193
+ return (_options$initialValue = options.initialValue) === null || _options$initialValue === void 0 ? void 0 : _options$initialValue.map(value => value !== null && value !== void 0 ? value : '');
194
+ };
195
+ var [key, setKey] = useState(options.key);
196
+ var [value, setValue] = useState(initializeValue);
197
+ if (key !== options.key) {
198
+ setValue(initializeValue);
199
+ setKey(options.key);
200
+ }
201
+ return [value, setValue];
202
+ }
203
+ function useControl(meta) {
204
+ var [value, setValue] = useInputValue(meta);
205
+ var {
206
+ register,
207
+ change,
208
+ focus,
209
+ blur
210
+ } = useInputEvent();
211
+ var handleChange = value => {
212
+ setValue(value);
213
+ change(value);
214
+ };
215
+ return {
216
+ register,
217
+ value,
218
+ change: handleChange,
219
+ focus,
220
+ blur
221
+ };
222
+ }
223
+ function useInputControl(meta) {
224
+ var [value, setValue] = useInputValue(meta);
225
+ var initializedRef = useRef(false);
226
+ var {
227
+ register,
228
+ change,
229
+ focus,
230
+ blur
231
+ } = useInputEvent();
232
+ useEffect(() => {
233
+ var form = getFormElement(meta.formId);
234
+ if (!form) {
235
+ // eslint-disable-next-line no-console
236
+ console.warn("useInputControl is unable to find form#".concat(meta.formId, " and identify if a dummy input is required"));
237
+ return;
238
+ }
239
+ var element = getEventTarget(form, meta.name);
240
+ if (!element && typeof value !== 'undefined' && (!Array.isArray(value) || value.length > 0)) {
241
+ element = createDummySelect(form, meta.name, value);
242
+ }
243
+ register(element);
244
+ if (!initializedRef.current) {
245
+ initializedRef.current = true;
246
+ } else {
247
+ change(value !== null && value !== void 0 ? value : '');
248
+ }
249
+ return () => {
250
+ register(null);
251
+ var elements = getFieldElements(form, meta.name);
252
+ for (var _element of elements) {
253
+ if (isDummySelect(_element)) {
254
+ _element.remove();
255
+ }
256
+ }
257
+ };
258
+ }, [meta.formId, meta.name, value, change, register]);
259
+ return {
260
+ value,
261
+ change: setValue,
262
+ focus,
263
+ blur
264
+ };
265
+ }
266
+ function Control(props) {
267
+ var control = useControl(props.meta);
268
+ return props.render(control);
209
269
  }
210
270
 
211
- export { createDummySelect, getEventTarget, getFieldElements, getFormElement, isDummySelect, updateFieldValue, useInputControl };
271
+ export { Control, createDummySelect, getEventTarget, getFieldElements, getFormElement, isDummySelect, updateFieldValue, useControl, useInputControl, useInputEvent, useInputValue };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Conform view adapter for react",
4
4
  "homepage": "https://conform.guide",
5
5
  "license": "MIT",
6
- "version": "1.0.1",
6
+ "version": "1.0.3",
7
7
  "main": "index.js",
8
8
  "module": "index.mjs",
9
9
  "types": "index.d.ts",
@@ -30,14 +30,16 @@
30
30
  "url": "https://github.com/edmundhung/conform/issues"
31
31
  },
32
32
  "dependencies": {
33
- "@conform-to/dom": "1.0.1"
33
+ "@conform-to/dom": "1.0.3"
34
34
  },
35
35
  "devDependencies": {
36
- "@types/react": "^18.2.43",
36
+ "@types/react": "^18.2.64",
37
+ "@types/react-dom": "^18.2.20",
37
38
  "react": "^18.2.0"
38
39
  },
39
40
  "peerDependencies": {
40
- "react": ">=18"
41
+ "react": ">=18",
42
+ "react-dom": ">=18"
41
43
  },
42
44
  "keywords": [
43
45
  "constraint-validation",