@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/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
- export { type FieldsetConstraint, type Submission, parse, list, validate, requestIntent, isFieldElement, } from '@conform-to/dom';
2
- export { type Fieldset, type FieldConfig, type FieldsetConfig, type FormConfig, useForm, useFieldset, useFieldList, useInputEvent, validateConstraint, } from './hooks.js';
3
- export * as conform from './helpers.js';
1
+ export { type Submission, type FieldName, requestIntent, isFieldElement, } from '@conform-to/dom';
2
+ export { type Field, type FieldMetadata as FieldConfig, FormProvider, FormStateInput, } from './context';
3
+ export { useForm, useFormMetadata, useFieldset, useFieldList, useField, } from './hooks';
4
+ export { useInputEvent } from './integrations';
5
+ export * as conform from './helpers';
6
+ export * as intent from './intent';
package/index.js CHANGED
@@ -3,8 +3,11 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var dom = require('@conform-to/dom');
6
+ var context = require('./context.js');
6
7
  var hooks = require('./hooks.js');
8
+ var integrations = require('./integrations.js');
7
9
  var helpers = require('./helpers.js');
10
+ var intent = require('./intent.js');
8
11
 
9
12
 
10
13
 
@@ -12,25 +15,17 @@ Object.defineProperty(exports, 'isFieldElement', {
12
15
  enumerable: true,
13
16
  get: function () { return dom.isFieldElement; }
14
17
  });
15
- Object.defineProperty(exports, 'list', {
16
- enumerable: true,
17
- get: function () { return dom.list; }
18
- });
19
- Object.defineProperty(exports, 'parse', {
20
- enumerable: true,
21
- get: function () { return dom.parse; }
22
- });
23
18
  Object.defineProperty(exports, 'requestIntent', {
24
19
  enumerable: true,
25
20
  get: function () { return dom.requestIntent; }
26
21
  });
27
- Object.defineProperty(exports, 'validate', {
28
- enumerable: true,
29
- get: function () { return dom.validate; }
30
- });
22
+ exports.FormProvider = context.FormProvider;
23
+ exports.FormStateInput = context.FormStateInput;
24
+ exports.useField = hooks.useField;
31
25
  exports.useFieldList = hooks.useFieldList;
32
26
  exports.useFieldset = hooks.useFieldset;
33
27
  exports.useForm = hooks.useForm;
34
- exports.useInputEvent = hooks.useInputEvent;
35
- exports.validateConstraint = hooks.validateConstraint;
28
+ exports.useFormMetadata = hooks.useFormMetadata;
29
+ exports.useInputEvent = integrations.useInputEvent;
36
30
  exports.conform = helpers;
31
+ exports.intent = intent;
package/index.mjs CHANGED
@@ -1,4 +1,8 @@
1
- export { isFieldElement, list, parse, requestIntent, validate } from '@conform-to/dom';
2
- export { useFieldList, useFieldset, useForm, useInputEvent, validateConstraint } from './hooks.mjs';
1
+ export { isFieldElement, requestIntent } from '@conform-to/dom';
2
+ export { FormProvider, FormStateInput } from './context.mjs';
3
+ export { useField, useFieldList, useFieldset, useForm, useFormMetadata } from './hooks.mjs';
4
+ export { useInputEvent } from './integrations.mjs';
3
5
  import * as helpers from './helpers.mjs';
4
6
  export { helpers as conform };
7
+ import * as intent from './intent.mjs';
8
+ export { intent };
@@ -0,0 +1,23 @@
1
+ import { type FieldElement } from '@conform-to/dom';
2
+ import { type RefObject } from 'react';
3
+ export type InputControl = {
4
+ change: (eventOrValue: {
5
+ target: {
6
+ value: string;
7
+ };
8
+ } | string | boolean) => void;
9
+ focus: () => void;
10
+ blur: () => void;
11
+ };
12
+ /**
13
+ * Returns a ref object and a set of helpers that dispatch corresponding dom event.
14
+ *
15
+ * @see https://conform.guide/api/react#useinputevent
16
+ */
17
+ export declare function useInputEvent(options: {
18
+ ref: RefObject<FieldElement> | (() => Element | RadioNodeList | FieldElement | null | undefined);
19
+ onInput?: (event: Event) => void;
20
+ onFocus?: (event: FocusEvent) => void;
21
+ onBlur?: (event: FocusEvent) => void;
22
+ onReset?: (event: Event) => void;
23
+ }): InputControl;
@@ -0,0 +1,145 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var dom = require('@conform-to/dom');
6
+ var react = require('react');
7
+ var hooks = require('./hooks.js');
8
+
9
+ /**
10
+ * Returns a ref object and a set of helpers that dispatch corresponding dom event.
11
+ *
12
+ * @see https://conform.guide/api/react#useinputevent
13
+ */
14
+ function useInputEvent(options) {
15
+ var optionsRef = react.useRef(options);
16
+ var eventDispatched = react.useRef({
17
+ onInput: false,
18
+ onFocus: false,
19
+ onBlur: false
20
+ });
21
+ hooks.useSafeLayoutEffect(() => {
22
+ optionsRef.current = options;
23
+ });
24
+ hooks.useSafeLayoutEffect(() => {
25
+ var createEventListener = listener => {
26
+ return event => {
27
+ var _optionsRef$current, _optionsRef$current2, _optionsRef$current3;
28
+ var element = typeof ((_optionsRef$current = optionsRef.current) === null || _optionsRef$current === void 0 ? void 0 : _optionsRef$current.ref) === 'function' ? (_optionsRef$current2 = optionsRef.current) === null || _optionsRef$current2 === void 0 ? void 0 : _optionsRef$current2.ref() : (_optionsRef$current3 = optionsRef.current) === null || _optionsRef$current3 === void 0 ? void 0 : _optionsRef$current3.ref.current;
29
+ if (dom.isFieldElement(element) && (listener === 'onReset' ? event.target === element.form : event.target === element)) {
30
+ var _optionsRef$current4, _optionsRef$current4$;
31
+ if (listener !== 'onReset') {
32
+ eventDispatched.current[listener] = true;
33
+ }
34
+ (_optionsRef$current4 = optionsRef.current) === null || _optionsRef$current4 === void 0 || (_optionsRef$current4$ = _optionsRef$current4[listener]) === null || _optionsRef$current4$ === void 0 ? void 0 : _optionsRef$current4$.call(_optionsRef$current4, event);
35
+ }
36
+ };
37
+ };
38
+ var inputHandler = createEventListener('onInput');
39
+ var focusHandler = createEventListener('onFocus');
40
+ var blurHandler = createEventListener('onBlur');
41
+ var resetHandler = createEventListener('onReset');
42
+
43
+ // focus/blur event does not bubble
44
+ document.addEventListener('input', inputHandler, true);
45
+ document.addEventListener('focus', focusHandler, true);
46
+ document.addEventListener('blur', blurHandler, true);
47
+ document.addEventListener('reset', resetHandler);
48
+ return () => {
49
+ document.removeEventListener('input', inputHandler, true);
50
+ document.removeEventListener('focus', focusHandler, true);
51
+ document.removeEventListener('blur', blurHandler, true);
52
+ document.removeEventListener('reset', resetHandler);
53
+ };
54
+ }, []);
55
+ var control = react.useMemo(() => {
56
+ var dispatch = (listener, fn) => {
57
+ if (!eventDispatched.current[listener]) {
58
+ var _optionsRef$current5, _optionsRef$current6, _optionsRef$current7;
59
+ var _element = typeof ((_optionsRef$current5 = optionsRef.current) === null || _optionsRef$current5 === void 0 ? void 0 : _optionsRef$current5.ref) === 'function' ? (_optionsRef$current6 = optionsRef.current) === null || _optionsRef$current6 === void 0 ? void 0 : _optionsRef$current6.ref() : (_optionsRef$current7 = optionsRef.current) === null || _optionsRef$current7 === void 0 ? void 0 : _optionsRef$current7.ref.current;
60
+ if (!dom.isFieldElement(_element)) {
61
+ // eslint-disable-next-line no-console
62
+ console.warn('Failed to dispatch event; is the input mounted?');
63
+ return;
64
+ }
65
+
66
+ // To avoid recursion
67
+ eventDispatched.current[listener] = true;
68
+ fn(_element);
69
+ }
70
+ eventDispatched.current[listener] = false;
71
+ };
72
+ return {
73
+ change(eventOrValue) {
74
+ dispatch('onInput', element => {
75
+ if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {
76
+ if (typeof eventOrValue !== 'boolean') {
77
+ throw new Error('You should pass a boolean when changing a checkbox or radio input');
78
+ }
79
+ element.checked = eventOrValue;
80
+ } else {
81
+ if (typeof eventOrValue === 'boolean') {
82
+ throw new Error('You can pass a boolean only when changing a checkbox or radio input');
83
+ }
84
+ var value = typeof eventOrValue === 'string' ? eventOrValue : eventOrValue.target.value;
85
+
86
+ // No change event will triggered on React if `element.value` is updated
87
+ // before dispatching the event
88
+ if (element.value !== value) {
89
+ /**
90
+ * Triggering react custom change event
91
+ * Solution based on dom-testing-library
92
+ * @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
93
+ * @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
94
+ */
95
+ var {
96
+ set: valueSetter
97
+ } = Object.getOwnPropertyDescriptor(element, 'value') || {};
98
+ var prototype = Object.getPrototypeOf(element);
99
+ var {
100
+ set: prototypeValueSetter
101
+ } = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
102
+ if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
103
+ prototypeValueSetter.call(element, value);
104
+ } else {
105
+ if (valueSetter) {
106
+ valueSetter.call(element, value);
107
+ } else {
108
+ throw new Error('The given element does not have a value setter');
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ // Dispatch input event with the updated input value
115
+ element.dispatchEvent(new InputEvent('input', {
116
+ bubbles: true
117
+ }));
118
+ // Dispatch change event (necessary for select to update the selected option)
119
+ element.dispatchEvent(new Event('change', {
120
+ bubbles: true
121
+ }));
122
+ });
123
+ },
124
+ focus() {
125
+ dispatch('onFocus', element => {
126
+ element.dispatchEvent(new FocusEvent('focusin', {
127
+ bubbles: true
128
+ }));
129
+ element.dispatchEvent(new FocusEvent('focus'));
130
+ });
131
+ },
132
+ blur() {
133
+ dispatch('onBlur', element => {
134
+ element.dispatchEvent(new FocusEvent('focusout', {
135
+ bubbles: true
136
+ }));
137
+ element.dispatchEvent(new FocusEvent('blur'));
138
+ });
139
+ }
140
+ };
141
+ }, [optionsRef]);
142
+ return control;
143
+ }
144
+
145
+ exports.useInputEvent = useInputEvent;
@@ -0,0 +1,141 @@
1
+ import { isFieldElement } from '@conform-to/dom';
2
+ import { useRef, useMemo } from 'react';
3
+ import { useSafeLayoutEffect } from './hooks.mjs';
4
+
5
+ /**
6
+ * Returns a ref object and a set of helpers that dispatch corresponding dom event.
7
+ *
8
+ * @see https://conform.guide/api/react#useinputevent
9
+ */
10
+ function useInputEvent(options) {
11
+ var optionsRef = useRef(options);
12
+ var eventDispatched = useRef({
13
+ onInput: false,
14
+ onFocus: false,
15
+ onBlur: false
16
+ });
17
+ useSafeLayoutEffect(() => {
18
+ optionsRef.current = options;
19
+ });
20
+ useSafeLayoutEffect(() => {
21
+ var createEventListener = listener => {
22
+ return event => {
23
+ var _optionsRef$current, _optionsRef$current2, _optionsRef$current3;
24
+ var element = typeof ((_optionsRef$current = optionsRef.current) === null || _optionsRef$current === void 0 ? void 0 : _optionsRef$current.ref) === 'function' ? (_optionsRef$current2 = optionsRef.current) === null || _optionsRef$current2 === void 0 ? void 0 : _optionsRef$current2.ref() : (_optionsRef$current3 = optionsRef.current) === null || _optionsRef$current3 === void 0 ? void 0 : _optionsRef$current3.ref.current;
25
+ if (isFieldElement(element) && (listener === 'onReset' ? event.target === element.form : event.target === element)) {
26
+ var _optionsRef$current4, _optionsRef$current4$;
27
+ if (listener !== 'onReset') {
28
+ eventDispatched.current[listener] = true;
29
+ }
30
+ (_optionsRef$current4 = optionsRef.current) === null || _optionsRef$current4 === void 0 || (_optionsRef$current4$ = _optionsRef$current4[listener]) === null || _optionsRef$current4$ === void 0 ? void 0 : _optionsRef$current4$.call(_optionsRef$current4, event);
31
+ }
32
+ };
33
+ };
34
+ var inputHandler = createEventListener('onInput');
35
+ var focusHandler = createEventListener('onFocus');
36
+ var blurHandler = createEventListener('onBlur');
37
+ var resetHandler = createEventListener('onReset');
38
+
39
+ // focus/blur event does not bubble
40
+ document.addEventListener('input', inputHandler, true);
41
+ document.addEventListener('focus', focusHandler, true);
42
+ document.addEventListener('blur', blurHandler, true);
43
+ document.addEventListener('reset', resetHandler);
44
+ return () => {
45
+ document.removeEventListener('input', inputHandler, true);
46
+ document.removeEventListener('focus', focusHandler, true);
47
+ document.removeEventListener('blur', blurHandler, true);
48
+ document.removeEventListener('reset', resetHandler);
49
+ };
50
+ }, []);
51
+ var control = useMemo(() => {
52
+ var dispatch = (listener, fn) => {
53
+ if (!eventDispatched.current[listener]) {
54
+ var _optionsRef$current5, _optionsRef$current6, _optionsRef$current7;
55
+ var _element = typeof ((_optionsRef$current5 = optionsRef.current) === null || _optionsRef$current5 === void 0 ? void 0 : _optionsRef$current5.ref) === 'function' ? (_optionsRef$current6 = optionsRef.current) === null || _optionsRef$current6 === void 0 ? void 0 : _optionsRef$current6.ref() : (_optionsRef$current7 = optionsRef.current) === null || _optionsRef$current7 === void 0 ? void 0 : _optionsRef$current7.ref.current;
56
+ if (!isFieldElement(_element)) {
57
+ // eslint-disable-next-line no-console
58
+ console.warn('Failed to dispatch event; is the input mounted?');
59
+ return;
60
+ }
61
+
62
+ // To avoid recursion
63
+ eventDispatched.current[listener] = true;
64
+ fn(_element);
65
+ }
66
+ eventDispatched.current[listener] = false;
67
+ };
68
+ return {
69
+ change(eventOrValue) {
70
+ dispatch('onInput', element => {
71
+ if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {
72
+ if (typeof eventOrValue !== 'boolean') {
73
+ throw new Error('You should pass a boolean when changing a checkbox or radio input');
74
+ }
75
+ element.checked = eventOrValue;
76
+ } else {
77
+ if (typeof eventOrValue === 'boolean') {
78
+ throw new Error('You can pass a boolean only when changing a checkbox or radio input');
79
+ }
80
+ var value = typeof eventOrValue === 'string' ? eventOrValue : eventOrValue.target.value;
81
+
82
+ // No change event will triggered on React if `element.value` is updated
83
+ // before dispatching the event
84
+ if (element.value !== value) {
85
+ /**
86
+ * Triggering react custom change event
87
+ * Solution based on dom-testing-library
88
+ * @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
89
+ * @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
90
+ */
91
+ var {
92
+ set: valueSetter
93
+ } = Object.getOwnPropertyDescriptor(element, 'value') || {};
94
+ var prototype = Object.getPrototypeOf(element);
95
+ var {
96
+ set: prototypeValueSetter
97
+ } = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
98
+ if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
99
+ prototypeValueSetter.call(element, value);
100
+ } else {
101
+ if (valueSetter) {
102
+ valueSetter.call(element, value);
103
+ } else {
104
+ throw new Error('The given element does not have a value setter');
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ // Dispatch input event with the updated input value
111
+ element.dispatchEvent(new InputEvent('input', {
112
+ bubbles: true
113
+ }));
114
+ // Dispatch change event (necessary for select to update the selected option)
115
+ element.dispatchEvent(new Event('change', {
116
+ bubbles: true
117
+ }));
118
+ });
119
+ },
120
+ focus() {
121
+ dispatch('onFocus', element => {
122
+ element.dispatchEvent(new FocusEvent('focusin', {
123
+ bubbles: true
124
+ }));
125
+ element.dispatchEvent(new FocusEvent('focus'));
126
+ });
127
+ },
128
+ blur() {
129
+ dispatch('onBlur', element => {
130
+ element.dispatchEvent(new FocusEvent('focusout', {
131
+ bubbles: true
132
+ }));
133
+ element.dispatchEvent(new FocusEvent('blur'));
134
+ });
135
+ }
136
+ };
137
+ }, [optionsRef]);
138
+ return control;
139
+ }
140
+
141
+ export { useInputEvent };
package/intent.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { type ListIntentPayload } from '@conform-to/dom';
2
+ import type { Field, Pretty } from './context';
3
+ declare function createIntentButtonProps(value: string, form?: string): {
4
+ name: string;
5
+ value: string;
6
+ form: string | undefined;
7
+ formNoValidate: boolean;
8
+ };
9
+ export declare function validate(field: Field<unknown>): {
10
+ name: string;
11
+ value: string;
12
+ form: string | undefined;
13
+ formNoValidate: boolean;
14
+ };
15
+ type ExtractListIntentPayload<Operation, Schema = unknown> = Pretty<Omit<Extract<ListIntentPayload<Schema>, {
16
+ operation: Operation;
17
+ }>, 'name' | 'operation'>>;
18
+ type ListIntent<Operation> = {} extends ExtractListIntentPayload<Operation> ? <Item>(name: Field<Array<Item>>, payload?: ExtractListIntentPayload<Operation, Item>) => ReturnType<typeof createIntentButtonProps> : <Item>(field: Field<Array<Item>>, payload: ExtractListIntentPayload<Operation, Item>) => ReturnType<typeof createIntentButtonProps>;
19
+ /**
20
+ * Helpers to configure an intent button for modifying a list
21
+ *
22
+ * @see https://conform.guide/api/react#list
23
+ */
24
+ export declare const list: {
25
+ /**
26
+ * @deprecated You can use `insert` without specifying an index instead
27
+ */
28
+ append: ListIntent<'append'>;
29
+ /**
30
+ * @deprecated You can use `insert` with zero index instead
31
+ */
32
+ prepend: ListIntent<'prepend'>;
33
+ insert: ListIntent<'insert'>;
34
+ replace: ListIntent<'replace'>;
35
+ remove: ListIntent<'remove'>;
36
+ reorder: ListIntent<'reorder'>;
37
+ };
38
+ export {};
package/intent.js ADDED
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
6
+ var dom = require('@conform-to/dom');
7
+
8
+ function createIntentButtonProps(value, form) {
9
+ return {
10
+ name: dom.INTENT,
11
+ value,
12
+ form,
13
+ formNoValidate: true
14
+ };
15
+ }
16
+ function validate(field) {
17
+ return createIntentButtonProps(dom.validate.serialize(field.name), field.formId);
18
+ }
19
+ /**
20
+ * Helpers to configure an intent button for modifying a list
21
+ *
22
+ * @see https://conform.guide/api/react#list
23
+ */
24
+ var list = new Proxy({}, {
25
+ get(_target, operation) {
26
+ return function (field) {
27
+ var payload = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
28
+ return createIntentButtonProps(dom.list.serialize(_rollupPluginBabelHelpers.objectSpread2({
29
+ name: field.name,
30
+ operation
31
+ }, payload)), field.formId);
32
+ };
33
+ }
34
+ });
35
+
36
+ exports.list = list;
37
+ exports.validate = validate;
package/intent.mjs ADDED
@@ -0,0 +1,32 @@
1
+ import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
2
+ import { validate as validate$1, list as list$1, INTENT } from '@conform-to/dom';
3
+
4
+ function createIntentButtonProps(value, form) {
5
+ return {
6
+ name: INTENT,
7
+ value,
8
+ form,
9
+ formNoValidate: true
10
+ };
11
+ }
12
+ function validate(field) {
13
+ return createIntentButtonProps(validate$1.serialize(field.name), field.formId);
14
+ }
15
+ /**
16
+ * Helpers to configure an intent button for modifying a list
17
+ *
18
+ * @see https://conform.guide/api/react#list
19
+ */
20
+ var list = new Proxy({}, {
21
+ get(_target, operation) {
22
+ return function (field) {
23
+ var payload = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
24
+ return createIntentButtonProps(list$1.serialize(_objectSpread2({
25
+ name: field.name,
26
+ operation
27
+ }, payload)), field.formId);
28
+ };
29
+ }
30
+ });
31
+
32
+ export { list, validate };
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": "0.9.0",
6
+ "version": "1.0.0-pre.0",
7
7
  "main": "index.js",
8
8
  "module": "index.mjs",
9
9
  "types": "index.d.ts",
@@ -30,10 +30,10 @@
30
30
  "url": "https://github.com/edmundhung/conform/issues"
31
31
  },
32
32
  "dependencies": {
33
- "@conform-to/dom": "0.9.0"
33
+ "@conform-to/dom": "1.0.0-pre.0"
34
34
  },
35
35
  "peerDependencies": {
36
- "react": ">=16.8"
36
+ "react": ">=18"
37
37
  },
38
38
  "keywords": [
39
39
  "constraint-validation",