@conform-to/react 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Edmund Hung
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # @conform-to/react
2
+
3
+ > View adapter for [react](https://github.com/facebook/react), built on top of `@conform-to/dom`
4
+
5
+ ## API Reference
6
+
7
+ - [conform](#conform)
8
+ - [useControlledInput](#useControlledInput)
9
+ - [useForm](#useForm)
10
+ - [useFieldset](#useFieldset)
11
+ - [useFieldList](#useFieldList)
12
+
13
+ ### conform
14
+
15
+ ### useControlledInput
16
+
17
+ ### useForm
18
+
19
+ ### useFieldset
20
+
21
+ ### useFieldList
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ function ownKeys(object, enumerableOnly) {
6
+ var keys = Object.keys(object);
7
+
8
+ if (Object.getOwnPropertySymbols) {
9
+ var symbols = Object.getOwnPropertySymbols(object);
10
+ enumerableOnly && (symbols = symbols.filter(function (sym) {
11
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
12
+ })), keys.push.apply(keys, symbols);
13
+ }
14
+
15
+ return keys;
16
+ }
17
+
18
+ function _objectSpread2(target) {
19
+ for (var i = 1; i < arguments.length; i++) {
20
+ var source = null != arguments[i] ? arguments[i] : {};
21
+ i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
22
+ _defineProperty(target, key, source[key]);
23
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
24
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
25
+ });
26
+ }
27
+
28
+ return target;
29
+ }
30
+
31
+ function _defineProperty(obj, key, value) {
32
+ if (key in obj) {
33
+ Object.defineProperty(obj, key, {
34
+ value: value,
35
+ enumerable: true,
36
+ configurable: true,
37
+ writable: true
38
+ });
39
+ } else {
40
+ obj[key] = value;
41
+ }
42
+
43
+ return obj;
44
+ }
45
+
46
+ exports.defineProperty = _defineProperty;
47
+ exports.objectSpread2 = _objectSpread2;
package/helpers.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { type FieldConfig } from '@conform-to/dom';
2
+ import { type InputHTMLAttributes, type SelectHTMLAttributes, type TextareaHTMLAttributes } from 'react';
3
+ export declare function input<Type extends string | number | Date | undefined>(config: FieldConfig<Type>, { type, value }?: {
4
+ type?: string;
5
+ value?: string;
6
+ }): InputHTMLAttributes<HTMLInputElement>;
7
+ export declare function select<T extends any>(config: FieldConfig<T>): SelectHTMLAttributes<HTMLSelectElement>;
8
+ export declare function textarea<T extends string | undefined>(config: FieldConfig<T>): TextareaHTMLAttributes<HTMLTextAreaElement>;
package/helpers.js ADDED
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ function input(config) {
6
+ var _config$initialValue, _config$constraint, _config$constraint2, _config$constraint3, _config$constraint4, _config$constraint5, _config$constraint6, _config$constraint7;
7
+
8
+ var {
9
+ type,
10
+ value
11
+ } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
12
+ var isCheckboxOrRadio = type === 'checkbox' || type === 'radio';
13
+ return {
14
+ type,
15
+ name: config.name,
16
+ form: config.form,
17
+ value: isCheckboxOrRadio ? value : undefined,
18
+ defaultValue: !isCheckboxOrRadio ? "".concat((_config$initialValue = config.initialValue) !== null && _config$initialValue !== void 0 ? _config$initialValue : '') : undefined,
19
+ defaultChecked: isCheckboxOrRadio ? config.initialValue === value : undefined,
20
+ required: (_config$constraint = config.constraint) === null || _config$constraint === void 0 ? void 0 : _config$constraint.required,
21
+ minLength: (_config$constraint2 = config.constraint) === null || _config$constraint2 === void 0 ? void 0 : _config$constraint2.minLength,
22
+ maxLength: (_config$constraint3 = config.constraint) === null || _config$constraint3 === void 0 ? void 0 : _config$constraint3.maxLength,
23
+ min: (_config$constraint4 = config.constraint) === null || _config$constraint4 === void 0 ? void 0 : _config$constraint4.min,
24
+ max: (_config$constraint5 = config.constraint) === null || _config$constraint5 === void 0 ? void 0 : _config$constraint5.max,
25
+ step: (_config$constraint6 = config.constraint) === null || _config$constraint6 === void 0 ? void 0 : _config$constraint6.step,
26
+ pattern: (_config$constraint7 = config.constraint) === null || _config$constraint7 === void 0 ? void 0 : _config$constraint7.pattern
27
+ };
28
+ }
29
+ function select(config) {
30
+ var _config$initialValue2, _config$constraint8, _config$constraint9;
31
+
32
+ return {
33
+ name: config.name,
34
+ form: config.form,
35
+ defaultValue: "".concat((_config$initialValue2 = config.initialValue) !== null && _config$initialValue2 !== void 0 ? _config$initialValue2 : ''),
36
+ required: (_config$constraint8 = config.constraint) === null || _config$constraint8 === void 0 ? void 0 : _config$constraint8.required,
37
+ multiple: (_config$constraint9 = config.constraint) === null || _config$constraint9 === void 0 ? void 0 : _config$constraint9.multiple
38
+ };
39
+ }
40
+ function textarea(config) {
41
+ var _config$initialValue3, _config$constraint10, _config$constraint11, _config$constraint12;
42
+
43
+ return {
44
+ name: config.name,
45
+ form: config.form,
46
+ defaultValue: "".concat((_config$initialValue3 = config.initialValue) !== null && _config$initialValue3 !== void 0 ? _config$initialValue3 : ''),
47
+ required: (_config$constraint10 = config.constraint) === null || _config$constraint10 === void 0 ? void 0 : _config$constraint10.required,
48
+ minLength: (_config$constraint11 = config.constraint) === null || _config$constraint11 === void 0 ? void 0 : _config$constraint11.minLength,
49
+ maxLength: (_config$constraint12 = config.constraint) === null || _config$constraint12 === void 0 ? void 0 : _config$constraint12.maxLength
50
+ };
51
+ }
52
+
53
+ exports.input = input;
54
+ exports.select = select;
55
+ exports.textarea = textarea;
package/hooks.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { type FieldConfig, type Schema } from '@conform-to/dom';
2
+ import { type ButtonHTMLAttributes, type FormEventHandler, type FormHTMLAttributes, type RefObject, type ReactElement } from 'react';
3
+ interface FormConfig {
4
+ /**
5
+ * Decide when the error should be reported initially.
6
+ * Default to `onSubmit`
7
+ */
8
+ initialReport?: 'onSubmit' | 'onChange' | 'onBlur';
9
+ /**
10
+ * Native browser report will be used before hydation if it is set to `true`.
11
+ * Default to `false`
12
+ */
13
+ fallbackNative?: boolean;
14
+ /**
15
+ * The form could be submitted even if there is invalid input control if it is set to `true`.
16
+ * Default to `false`
17
+ */
18
+ noValidate?: boolean;
19
+ /**
20
+ * The submit handler will be triggered only when the form is valid.
21
+ * Or when noValidate is set to `true`
22
+ */
23
+ onSubmit?: FormHTMLAttributes<HTMLFormElement>['onSubmit'];
24
+ onReset?: FormHTMLAttributes<HTMLFormElement>['onReset'];
25
+ }
26
+ interface FormProps {
27
+ ref: RefObject<HTMLFormElement>;
28
+ onSubmit: Required<FormHTMLAttributes<HTMLFormElement>>['onSubmit'];
29
+ onReset: Required<FormHTMLAttributes<HTMLFormElement>>['onReset'];
30
+ noValidate: Required<FormHTMLAttributes<HTMLFormElement>>['noValidate'];
31
+ }
32
+ export declare function useForm({ onReset, onSubmit, noValidate, fallbackNative, initialReport, }?: FormConfig): FormProps;
33
+ interface FieldsetConfig<Type> extends Partial<FieldConfig<Type>> {
34
+ }
35
+ interface FieldsetProps {
36
+ ref: RefObject<HTMLFieldSetElement>;
37
+ name?: string;
38
+ form?: string;
39
+ onInput: FormEventHandler<HTMLFieldSetElement>;
40
+ onReset: FormEventHandler<HTMLFieldSetElement>;
41
+ onInvalid: FormEventHandler<HTMLFieldSetElement>;
42
+ }
43
+ export declare function useFieldset<Type extends Record<string, any>>(schema: Schema<Type>, config?: FieldsetConfig<Type>): [FieldsetProps, {
44
+ [Key in keyof Type]-?: FieldConfig<Type[Key]>;
45
+ }];
46
+ interface FieldListControl {
47
+ prepend(): ButtonHTMLAttributes<HTMLButtonElement>;
48
+ append(): ButtonHTMLAttributes<HTMLButtonElement>;
49
+ remove(index: number): ButtonHTMLAttributes<HTMLButtonElement>;
50
+ }
51
+ export declare function useFieldList<Type extends Array<any>>(config: FieldConfig<Type>): [
52
+ Array<{
53
+ key: string;
54
+ config: FieldConfig<Type extends Array<infer InnerType> ? InnerType : never>;
55
+ }>,
56
+ FieldListControl
57
+ ];
58
+ interface InputControl {
59
+ value: string;
60
+ onChange: (value: string) => void;
61
+ onBlur: () => void;
62
+ }
63
+ export declare function useControlledInput<T extends string | number | Date | undefined>(field: FieldConfig<T>): [ReactElement, InputControl];
64
+ export {};
package/hooks.js ADDED
@@ -0,0 +1,383 @@
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
+ var react = require('react');
8
+
9
+ function useForm() {
10
+ var {
11
+ onReset,
12
+ onSubmit,
13
+ noValidate = false,
14
+ fallbackNative = false,
15
+ initialReport = 'onSubmit'
16
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
17
+ var ref = react.useRef(null);
18
+ var [formNoValidate, setFormNoValidate] = react.useState(noValidate || !fallbackNative);
19
+
20
+ var handleSubmit = event => {
21
+ if (!noValidate) {
22
+ dom.setFieldState(event.currentTarget, {
23
+ touched: true
24
+ });
25
+
26
+ if (!dom.shouldSkipValidate(event.nativeEvent) && !event.currentTarget.reportValidity()) {
27
+ return event.preventDefault();
28
+ }
29
+ }
30
+
31
+ onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(event);
32
+ };
33
+
34
+ var handleReset = event => {
35
+ dom.setFieldState(event.currentTarget, {
36
+ touched: false
37
+ });
38
+ onReset === null || onReset === void 0 ? void 0 : onReset(event);
39
+ };
40
+
41
+ react.useEffect(() => {
42
+ setFormNoValidate(true);
43
+ }, []);
44
+ react.useEffect(() => {
45
+ if (noValidate) {
46
+ return;
47
+ }
48
+
49
+ var handleChange = event => {
50
+ var _event$target;
51
+
52
+ if (!dom.isFieldElement(event.target) || ((_event$target = event.target) === null || _event$target === void 0 ? void 0 : _event$target.form) !== ref.current) {
53
+ return;
54
+ }
55
+
56
+ if (initialReport === 'onChange') {
57
+ dom.setFieldState(event.target, {
58
+ touched: true
59
+ });
60
+ }
61
+
62
+ if (ref.current) {
63
+ dom.reportValidity(ref.current);
64
+ }
65
+ };
66
+
67
+ var handleBlur = event => {
68
+ var _event$target2;
69
+
70
+ if (!dom.isFieldElement(event.target) || ((_event$target2 = event.target) === null || _event$target2 === void 0 ? void 0 : _event$target2.form) !== ref.current) {
71
+ return;
72
+ }
73
+
74
+ if (initialReport === 'onBlur') {
75
+ dom.setFieldState(event.target, {
76
+ touched: true
77
+ });
78
+ }
79
+
80
+ if (ref.current) {
81
+ dom.reportValidity(ref.current);
82
+ }
83
+ };
84
+
85
+ document.body.addEventListener('input', handleChange);
86
+ document.body.addEventListener('focusout', handleBlur);
87
+ return () => {
88
+ document.body.removeEventListener('input', handleChange);
89
+ document.body.removeEventListener('focusout', handleBlur);
90
+ };
91
+ }, [noValidate, initialReport]);
92
+ return {
93
+ ref,
94
+ onSubmit: handleSubmit,
95
+ onReset: handleReset,
96
+ noValidate: formNoValidate
97
+ };
98
+ }
99
+ function useFieldset(schema) {
100
+ var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
101
+ var ref = react.useRef(null);
102
+ var [errorMessage, dispatch] = react.useReducer((state, action) => {
103
+ switch (action.type) {
104
+ case 'report':
105
+ {
106
+ var {
107
+ key,
108
+ message
109
+ } = action.payload;
110
+
111
+ if (state[key] === message) {
112
+ return state;
113
+ }
114
+
115
+ return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, state), {}, {
116
+ [key]: message
117
+ });
118
+ }
119
+
120
+ case 'migrate':
121
+ {
122
+ var {
123
+ keys,
124
+ error
125
+ } = action.payload;
126
+ var nextState = state;
127
+
128
+ for (var _key of Object.keys(keys)) {
129
+ var prevError = state[_key];
130
+ var nextError = error === null || error === void 0 ? void 0 : error[_key];
131
+
132
+ if (typeof nextError === 'string' && prevError !== nextError) {
133
+ return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, nextState), {}, {
134
+ [_key]: nextError
135
+ });
136
+ }
137
+ }
138
+
139
+ return nextState;
140
+ }
141
+
142
+ case 'cleanup':
143
+ {
144
+ var {
145
+ fieldset
146
+ } = action.payload;
147
+ var updates = [];
148
+
149
+ for (var [_key2, _message] of Object.entries(state)) {
150
+ if (!_message) {
151
+ continue;
152
+ }
153
+
154
+ var fields = dom.getFieldElements(fieldset, _key2);
155
+
156
+ if (fields.every(field => field.validity.valid)) {
157
+ updates.push([_key2, '']);
158
+ }
159
+ }
160
+
161
+ if (updates.length === 0) {
162
+ return state;
163
+ }
164
+
165
+ return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, state), Object.fromEntries(updates));
166
+ }
167
+
168
+ case 'reset':
169
+ {
170
+ return {};
171
+ }
172
+ }
173
+ }, {}, () => Object.fromEntries(Object.keys(schema.fields).reduce((result, name) => {
174
+ var _config$error;
175
+
176
+ var error = (_config$error = config.error) === null || _config$error === void 0 ? void 0 : _config$error[name];
177
+
178
+ if (typeof error === 'string') {
179
+ result.push([name, error]);
180
+ }
181
+
182
+ return result;
183
+ }, [])));
184
+ react.useEffect(() => {
185
+ var _schema$validate;
186
+
187
+ var fieldset = ref.current;
188
+
189
+ if (!fieldset) {
190
+ console.warn('No fieldset ref found; You must pass the fieldsetProps to the fieldset element');
191
+ return;
192
+ }
193
+
194
+ if (!(fieldset !== null && fieldset !== void 0 && fieldset.form)) {
195
+ console.warn('No form element is linked to the fieldset; Do you forgot setting the form attribute?');
196
+ }
197
+
198
+ (_schema$validate = schema.validate) === null || _schema$validate === void 0 ? void 0 : _schema$validate.call(schema, fieldset);
199
+ dispatch({
200
+ type: 'cleanup',
201
+ payload: {
202
+ fieldset
203
+ }
204
+ });
205
+ }, // eslint-disable-next-line react-hooks/exhaustive-deps
206
+ [schema.validate]);
207
+ react.useEffect(() => {
208
+ dispatch({
209
+ type: 'migrate',
210
+ payload: {
211
+ keys: Object.keys(schema.fields),
212
+ error: config.error
213
+ }
214
+ });
215
+ }, [config.error, schema.fields]);
216
+ return [{
217
+ ref,
218
+ name: config.name,
219
+ form: config.form,
220
+
221
+ onInput(e) {
222
+ var _schema$validate2;
223
+
224
+ var fieldset = e.currentTarget;
225
+ (_schema$validate2 = schema.validate) === null || _schema$validate2 === void 0 ? void 0 : _schema$validate2.call(schema, fieldset);
226
+ dispatch({
227
+ type: 'cleanup',
228
+ payload: {
229
+ fieldset
230
+ }
231
+ });
232
+ },
233
+
234
+ onReset(e) {
235
+ dom.setFieldState(e.currentTarget, {
236
+ touched: false
237
+ });
238
+ dispatch({
239
+ type: 'reset'
240
+ });
241
+ },
242
+
243
+ onInvalid(e) {
244
+ var element = dom.isFieldElement(e.target) ? e.target : null;
245
+ var key = Object.keys(schema.fields).find(key => (element === null || element === void 0 ? void 0 : element.name) === dom.getName([e.currentTarget.name, key]));
246
+
247
+ if (!element || !key) {
248
+ return;
249
+ } // Disable browser report
250
+
251
+
252
+ e.preventDefault();
253
+ dispatch({
254
+ type: 'report',
255
+ payload: {
256
+ key,
257
+ message: element.validationMessage
258
+ }
259
+ });
260
+ }
261
+
262
+ }, dom.createFieldConfig(schema, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, config), {}, {
263
+ error: Object.assign({}, config.error, errorMessage)
264
+ }))];
265
+ }
266
+ function useFieldList(config) {
267
+ var _config$initialValue$, _config$initialValue;
268
+
269
+ var size = (_config$initialValue$ = (_config$initialValue = config.initialValue) === null || _config$initialValue === void 0 ? void 0 : _config$initialValue.length) !== null && _config$initialValue$ !== void 0 ? _config$initialValue$ : 1;
270
+ var [keys, setKeys] = react.useState(() => [...Array(size).keys()]);
271
+ var list = react.useMemo(() => keys.map((key, index) => {
272
+ var _config$initialValue2, _config$error2;
273
+
274
+ return {
275
+ key: "".concat(key),
276
+ config: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, config), {}, {
277
+ name: "".concat(config.name, "[").concat(index, "]"),
278
+ initialValue: (_config$initialValue2 = config.initialValue) === null || _config$initialValue2 === void 0 ? void 0 : _config$initialValue2[index],
279
+ error: (_config$error2 = config.error) === null || _config$error2 === void 0 ? void 0 : _config$error2[index],
280
+ // @ts-expect-error
281
+ constraint: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, config.constraint), {}, {
282
+ multiple: false
283
+ })
284
+ })
285
+ };
286
+ }), [keys, config]);
287
+ var controls = {
288
+ prepend() {
289
+ return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, dom.createControlButton(config.name, 'prepend', {})), {}, {
290
+ onClick(e) {
291
+ setKeys(keys => [Date.now(), ...keys]);
292
+ e.preventDefault();
293
+ }
294
+
295
+ });
296
+ },
297
+
298
+ append() {
299
+ return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, dom.createControlButton(config.name, 'append', {})), {}, {
300
+ onClick(e) {
301
+ setKeys(keys => [...keys, Date.now()]);
302
+ e.preventDefault();
303
+ }
304
+
305
+ });
306
+ },
307
+
308
+ remove(index) {
309
+ return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, dom.createControlButton(config.name, 'remove', {
310
+ index
311
+ })), {}, {
312
+ onClick(e) {
313
+ setKeys(keys => [...keys.slice(0, index), ...keys.slice(index + 1)]);
314
+ e.preventDefault();
315
+ }
316
+
317
+ });
318
+ }
319
+
320
+ };
321
+ react.useEffect(() => {
322
+ setKeys(keys => {
323
+ if (keys.length === size) {
324
+ return keys;
325
+ }
326
+
327
+ return [...Array(size).keys()];
328
+ });
329
+ }, [size]);
330
+ return [list, controls];
331
+ }
332
+ function useControlledInput(field) {
333
+ var _field$initialValue;
334
+
335
+ var [value, setValue] = react.useState("".concat((_field$initialValue = field.initialValue) !== null && _field$initialValue !== void 0 ? _field$initialValue : ''));
336
+ var [shouldBlur, setShouldBlur] = react.useState(false);
337
+ var ref = react.useRef(null);
338
+ var input = react.useMemo(() => /*#__PURE__*/react.createElement('input', _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, field.constraint), {}, {
339
+ ref,
340
+ name: field.name,
341
+ defaultValue: field.initialValue,
342
+ style: {
343
+ display: 'none'
344
+ },
345
+ 'aria-hidden': true
346
+ })), [field.constraint, field.name, field.initialValue]);
347
+ react.useEffect(() => {
348
+ if (!ref.current) {
349
+ return;
350
+ }
351
+
352
+ ref.current.value = value;
353
+ ref.current.dispatchEvent(new InputEvent('input', {
354
+ bubbles: true
355
+ }));
356
+ }, [value]);
357
+ react.useEffect(() => {
358
+ var _ref$current;
359
+
360
+ if (!shouldBlur) {
361
+ return;
362
+ }
363
+
364
+ (_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.dispatchEvent(new FocusEvent('focusout', {
365
+ bubbles: true
366
+ }));
367
+ setShouldBlur(false);
368
+ }, [shouldBlur]);
369
+ return [input, {
370
+ value,
371
+ onChange: value => {
372
+ setValue(value);
373
+ },
374
+ onBlur: () => {
375
+ setShouldBlur(true);
376
+ }
377
+ }];
378
+ }
379
+
380
+ exports.useControlledInput = useControlledInput;
381
+ exports.useFieldList = useFieldList;
382
+ exports.useFieldset = useFieldset;
383
+ exports.useForm = useForm;
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { type FieldConfig, type Schema, parse, getFieldElements, } from '@conform-to/dom';
2
+ export * from './hooks';
3
+ export * as conform from './helpers';
package/index.js ADDED
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var dom = require('@conform-to/dom');
6
+ var hooks = require('./hooks.js');
7
+ var helpers = require('./helpers.js');
8
+
9
+
10
+
11
+ Object.defineProperty(exports, 'getFieldElements', {
12
+ enumerable: true,
13
+ get: function () { return dom.getFieldElements; }
14
+ });
15
+ Object.defineProperty(exports, 'parse', {
16
+ enumerable: true,
17
+ get: function () { return dom.parse; }
18
+ });
19
+ exports.useControlledInput = hooks.useControlledInput;
20
+ exports.useFieldList = hooks.useFieldList;
21
+ exports.useFieldset = hooks.useFieldset;
22
+ exports.useForm = hooks.useForm;
23
+ exports.conform = helpers;
@@ -0,0 +1,42 @@
1
+ function ownKeys(object, enumerableOnly) {
2
+ var keys = Object.keys(object);
3
+
4
+ if (Object.getOwnPropertySymbols) {
5
+ var symbols = Object.getOwnPropertySymbols(object);
6
+ enumerableOnly && (symbols = symbols.filter(function (sym) {
7
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
8
+ })), keys.push.apply(keys, symbols);
9
+ }
10
+
11
+ return keys;
12
+ }
13
+
14
+ function _objectSpread2(target) {
15
+ for (var i = 1; i < arguments.length; i++) {
16
+ var source = null != arguments[i] ? arguments[i] : {};
17
+ i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
18
+ _defineProperty(target, key, source[key]);
19
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
20
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
21
+ });
22
+ }
23
+
24
+ return target;
25
+ }
26
+
27
+ function _defineProperty(obj, key, value) {
28
+ if (key in obj) {
29
+ Object.defineProperty(obj, key, {
30
+ value: value,
31
+ enumerable: true,
32
+ configurable: true,
33
+ writable: true
34
+ });
35
+ } else {
36
+ obj[key] = value;
37
+ }
38
+
39
+ return obj;
40
+ }
41
+
42
+ export { _defineProperty as defineProperty, _objectSpread2 as objectSpread2 };
@@ -0,0 +1,49 @@
1
+ function input(config) {
2
+ var _config$initialValue, _config$constraint, _config$constraint2, _config$constraint3, _config$constraint4, _config$constraint5, _config$constraint6, _config$constraint7;
3
+
4
+ var {
5
+ type,
6
+ value
7
+ } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
8
+ var isCheckboxOrRadio = type === 'checkbox' || type === 'radio';
9
+ return {
10
+ type,
11
+ name: config.name,
12
+ form: config.form,
13
+ value: isCheckboxOrRadio ? value : undefined,
14
+ defaultValue: !isCheckboxOrRadio ? "".concat((_config$initialValue = config.initialValue) !== null && _config$initialValue !== void 0 ? _config$initialValue : '') : undefined,
15
+ defaultChecked: isCheckboxOrRadio ? config.initialValue === value : undefined,
16
+ required: (_config$constraint = config.constraint) === null || _config$constraint === void 0 ? void 0 : _config$constraint.required,
17
+ minLength: (_config$constraint2 = config.constraint) === null || _config$constraint2 === void 0 ? void 0 : _config$constraint2.minLength,
18
+ maxLength: (_config$constraint3 = config.constraint) === null || _config$constraint3 === void 0 ? void 0 : _config$constraint3.maxLength,
19
+ min: (_config$constraint4 = config.constraint) === null || _config$constraint4 === void 0 ? void 0 : _config$constraint4.min,
20
+ max: (_config$constraint5 = config.constraint) === null || _config$constraint5 === void 0 ? void 0 : _config$constraint5.max,
21
+ step: (_config$constraint6 = config.constraint) === null || _config$constraint6 === void 0 ? void 0 : _config$constraint6.step,
22
+ pattern: (_config$constraint7 = config.constraint) === null || _config$constraint7 === void 0 ? void 0 : _config$constraint7.pattern
23
+ };
24
+ }
25
+ function select(config) {
26
+ var _config$initialValue2, _config$constraint8, _config$constraint9;
27
+
28
+ return {
29
+ name: config.name,
30
+ form: config.form,
31
+ defaultValue: "".concat((_config$initialValue2 = config.initialValue) !== null && _config$initialValue2 !== void 0 ? _config$initialValue2 : ''),
32
+ required: (_config$constraint8 = config.constraint) === null || _config$constraint8 === void 0 ? void 0 : _config$constraint8.required,
33
+ multiple: (_config$constraint9 = config.constraint) === null || _config$constraint9 === void 0 ? void 0 : _config$constraint9.multiple
34
+ };
35
+ }
36
+ function textarea(config) {
37
+ var _config$initialValue3, _config$constraint10, _config$constraint11, _config$constraint12;
38
+
39
+ return {
40
+ name: config.name,
41
+ form: config.form,
42
+ defaultValue: "".concat((_config$initialValue3 = config.initialValue) !== null && _config$initialValue3 !== void 0 ? _config$initialValue3 : ''),
43
+ required: (_config$constraint10 = config.constraint) === null || _config$constraint10 === void 0 ? void 0 : _config$constraint10.required,
44
+ minLength: (_config$constraint11 = config.constraint) === null || _config$constraint11 === void 0 ? void 0 : _config$constraint11.minLength,
45
+ maxLength: (_config$constraint12 = config.constraint) === null || _config$constraint12 === void 0 ? void 0 : _config$constraint12.maxLength
46
+ };
47
+ }
48
+
49
+ export { input, select, textarea };
@@ -0,0 +1,376 @@
1
+ import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
2
+ import { getFieldElements, setFieldState, isFieldElement, getName, createFieldConfig, shouldSkipValidate, reportValidity, createControlButton } from '@conform-to/dom';
3
+ import { useRef, useState, useEffect, useReducer, useMemo, createElement } from 'react';
4
+
5
+ function useForm() {
6
+ var {
7
+ onReset,
8
+ onSubmit,
9
+ noValidate = false,
10
+ fallbackNative = false,
11
+ initialReport = 'onSubmit'
12
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
13
+ var ref = useRef(null);
14
+ var [formNoValidate, setFormNoValidate] = useState(noValidate || !fallbackNative);
15
+
16
+ var handleSubmit = event => {
17
+ if (!noValidate) {
18
+ setFieldState(event.currentTarget, {
19
+ touched: true
20
+ });
21
+
22
+ if (!shouldSkipValidate(event.nativeEvent) && !event.currentTarget.reportValidity()) {
23
+ return event.preventDefault();
24
+ }
25
+ }
26
+
27
+ onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(event);
28
+ };
29
+
30
+ var handleReset = event => {
31
+ setFieldState(event.currentTarget, {
32
+ touched: false
33
+ });
34
+ onReset === null || onReset === void 0 ? void 0 : onReset(event);
35
+ };
36
+
37
+ useEffect(() => {
38
+ setFormNoValidate(true);
39
+ }, []);
40
+ useEffect(() => {
41
+ if (noValidate) {
42
+ return;
43
+ }
44
+
45
+ var handleChange = event => {
46
+ var _event$target;
47
+
48
+ if (!isFieldElement(event.target) || ((_event$target = event.target) === null || _event$target === void 0 ? void 0 : _event$target.form) !== ref.current) {
49
+ return;
50
+ }
51
+
52
+ if (initialReport === 'onChange') {
53
+ setFieldState(event.target, {
54
+ touched: true
55
+ });
56
+ }
57
+
58
+ if (ref.current) {
59
+ reportValidity(ref.current);
60
+ }
61
+ };
62
+
63
+ var handleBlur = event => {
64
+ var _event$target2;
65
+
66
+ if (!isFieldElement(event.target) || ((_event$target2 = event.target) === null || _event$target2 === void 0 ? void 0 : _event$target2.form) !== ref.current) {
67
+ return;
68
+ }
69
+
70
+ if (initialReport === 'onBlur') {
71
+ setFieldState(event.target, {
72
+ touched: true
73
+ });
74
+ }
75
+
76
+ if (ref.current) {
77
+ reportValidity(ref.current);
78
+ }
79
+ };
80
+
81
+ document.body.addEventListener('input', handleChange);
82
+ document.body.addEventListener('focusout', handleBlur);
83
+ return () => {
84
+ document.body.removeEventListener('input', handleChange);
85
+ document.body.removeEventListener('focusout', handleBlur);
86
+ };
87
+ }, [noValidate, initialReport]);
88
+ return {
89
+ ref,
90
+ onSubmit: handleSubmit,
91
+ onReset: handleReset,
92
+ noValidate: formNoValidate
93
+ };
94
+ }
95
+ function useFieldset(schema) {
96
+ var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
97
+ var ref = useRef(null);
98
+ var [errorMessage, dispatch] = useReducer((state, action) => {
99
+ switch (action.type) {
100
+ case 'report':
101
+ {
102
+ var {
103
+ key,
104
+ message
105
+ } = action.payload;
106
+
107
+ if (state[key] === message) {
108
+ return state;
109
+ }
110
+
111
+ return _objectSpread2(_objectSpread2({}, state), {}, {
112
+ [key]: message
113
+ });
114
+ }
115
+
116
+ case 'migrate':
117
+ {
118
+ var {
119
+ keys,
120
+ error
121
+ } = action.payload;
122
+ var nextState = state;
123
+
124
+ for (var _key of Object.keys(keys)) {
125
+ var prevError = state[_key];
126
+ var nextError = error === null || error === void 0 ? void 0 : error[_key];
127
+
128
+ if (typeof nextError === 'string' && prevError !== nextError) {
129
+ return _objectSpread2(_objectSpread2({}, nextState), {}, {
130
+ [_key]: nextError
131
+ });
132
+ }
133
+ }
134
+
135
+ return nextState;
136
+ }
137
+
138
+ case 'cleanup':
139
+ {
140
+ var {
141
+ fieldset
142
+ } = action.payload;
143
+ var updates = [];
144
+
145
+ for (var [_key2, _message] of Object.entries(state)) {
146
+ if (!_message) {
147
+ continue;
148
+ }
149
+
150
+ var fields = getFieldElements(fieldset, _key2);
151
+
152
+ if (fields.every(field => field.validity.valid)) {
153
+ updates.push([_key2, '']);
154
+ }
155
+ }
156
+
157
+ if (updates.length === 0) {
158
+ return state;
159
+ }
160
+
161
+ return _objectSpread2(_objectSpread2({}, state), Object.fromEntries(updates));
162
+ }
163
+
164
+ case 'reset':
165
+ {
166
+ return {};
167
+ }
168
+ }
169
+ }, {}, () => Object.fromEntries(Object.keys(schema.fields).reduce((result, name) => {
170
+ var _config$error;
171
+
172
+ var error = (_config$error = config.error) === null || _config$error === void 0 ? void 0 : _config$error[name];
173
+
174
+ if (typeof error === 'string') {
175
+ result.push([name, error]);
176
+ }
177
+
178
+ return result;
179
+ }, [])));
180
+ useEffect(() => {
181
+ var _schema$validate;
182
+
183
+ var fieldset = ref.current;
184
+
185
+ if (!fieldset) {
186
+ console.warn('No fieldset ref found; You must pass the fieldsetProps to the fieldset element');
187
+ return;
188
+ }
189
+
190
+ if (!(fieldset !== null && fieldset !== void 0 && fieldset.form)) {
191
+ console.warn('No form element is linked to the fieldset; Do you forgot setting the form attribute?');
192
+ }
193
+
194
+ (_schema$validate = schema.validate) === null || _schema$validate === void 0 ? void 0 : _schema$validate.call(schema, fieldset);
195
+ dispatch({
196
+ type: 'cleanup',
197
+ payload: {
198
+ fieldset
199
+ }
200
+ });
201
+ }, // eslint-disable-next-line react-hooks/exhaustive-deps
202
+ [schema.validate]);
203
+ useEffect(() => {
204
+ dispatch({
205
+ type: 'migrate',
206
+ payload: {
207
+ keys: Object.keys(schema.fields),
208
+ error: config.error
209
+ }
210
+ });
211
+ }, [config.error, schema.fields]);
212
+ return [{
213
+ ref,
214
+ name: config.name,
215
+ form: config.form,
216
+
217
+ onInput(e) {
218
+ var _schema$validate2;
219
+
220
+ var fieldset = e.currentTarget;
221
+ (_schema$validate2 = schema.validate) === null || _schema$validate2 === void 0 ? void 0 : _schema$validate2.call(schema, fieldset);
222
+ dispatch({
223
+ type: 'cleanup',
224
+ payload: {
225
+ fieldset
226
+ }
227
+ });
228
+ },
229
+
230
+ onReset(e) {
231
+ setFieldState(e.currentTarget, {
232
+ touched: false
233
+ });
234
+ dispatch({
235
+ type: 'reset'
236
+ });
237
+ },
238
+
239
+ onInvalid(e) {
240
+ var element = isFieldElement(e.target) ? e.target : null;
241
+ var key = Object.keys(schema.fields).find(key => (element === null || element === void 0 ? void 0 : element.name) === getName([e.currentTarget.name, key]));
242
+
243
+ if (!element || !key) {
244
+ return;
245
+ } // Disable browser report
246
+
247
+
248
+ e.preventDefault();
249
+ dispatch({
250
+ type: 'report',
251
+ payload: {
252
+ key,
253
+ message: element.validationMessage
254
+ }
255
+ });
256
+ }
257
+
258
+ }, createFieldConfig(schema, _objectSpread2(_objectSpread2({}, config), {}, {
259
+ error: Object.assign({}, config.error, errorMessage)
260
+ }))];
261
+ }
262
+ function useFieldList(config) {
263
+ var _config$initialValue$, _config$initialValue;
264
+
265
+ var size = (_config$initialValue$ = (_config$initialValue = config.initialValue) === null || _config$initialValue === void 0 ? void 0 : _config$initialValue.length) !== null && _config$initialValue$ !== void 0 ? _config$initialValue$ : 1;
266
+ var [keys, setKeys] = useState(() => [...Array(size).keys()]);
267
+ var list = useMemo(() => keys.map((key, index) => {
268
+ var _config$initialValue2, _config$error2;
269
+
270
+ return {
271
+ key: "".concat(key),
272
+ config: _objectSpread2(_objectSpread2({}, config), {}, {
273
+ name: "".concat(config.name, "[").concat(index, "]"),
274
+ initialValue: (_config$initialValue2 = config.initialValue) === null || _config$initialValue2 === void 0 ? void 0 : _config$initialValue2[index],
275
+ error: (_config$error2 = config.error) === null || _config$error2 === void 0 ? void 0 : _config$error2[index],
276
+ // @ts-expect-error
277
+ constraint: _objectSpread2(_objectSpread2({}, config.constraint), {}, {
278
+ multiple: false
279
+ })
280
+ })
281
+ };
282
+ }), [keys, config]);
283
+ var controls = {
284
+ prepend() {
285
+ return _objectSpread2(_objectSpread2({}, createControlButton(config.name, 'prepend', {})), {}, {
286
+ onClick(e) {
287
+ setKeys(keys => [Date.now(), ...keys]);
288
+ e.preventDefault();
289
+ }
290
+
291
+ });
292
+ },
293
+
294
+ append() {
295
+ return _objectSpread2(_objectSpread2({}, createControlButton(config.name, 'append', {})), {}, {
296
+ onClick(e) {
297
+ setKeys(keys => [...keys, Date.now()]);
298
+ e.preventDefault();
299
+ }
300
+
301
+ });
302
+ },
303
+
304
+ remove(index) {
305
+ return _objectSpread2(_objectSpread2({}, createControlButton(config.name, 'remove', {
306
+ index
307
+ })), {}, {
308
+ onClick(e) {
309
+ setKeys(keys => [...keys.slice(0, index), ...keys.slice(index + 1)]);
310
+ e.preventDefault();
311
+ }
312
+
313
+ });
314
+ }
315
+
316
+ };
317
+ useEffect(() => {
318
+ setKeys(keys => {
319
+ if (keys.length === size) {
320
+ return keys;
321
+ }
322
+
323
+ return [...Array(size).keys()];
324
+ });
325
+ }, [size]);
326
+ return [list, controls];
327
+ }
328
+ function useControlledInput(field) {
329
+ var _field$initialValue;
330
+
331
+ var [value, setValue] = useState("".concat((_field$initialValue = field.initialValue) !== null && _field$initialValue !== void 0 ? _field$initialValue : ''));
332
+ var [shouldBlur, setShouldBlur] = useState(false);
333
+ var ref = useRef(null);
334
+ var input = useMemo(() => /*#__PURE__*/createElement('input', _objectSpread2(_objectSpread2({}, field.constraint), {}, {
335
+ ref,
336
+ name: field.name,
337
+ defaultValue: field.initialValue,
338
+ style: {
339
+ display: 'none'
340
+ },
341
+ 'aria-hidden': true
342
+ })), [field.constraint, field.name, field.initialValue]);
343
+ useEffect(() => {
344
+ if (!ref.current) {
345
+ return;
346
+ }
347
+
348
+ ref.current.value = value;
349
+ ref.current.dispatchEvent(new InputEvent('input', {
350
+ bubbles: true
351
+ }));
352
+ }, [value]);
353
+ useEffect(() => {
354
+ var _ref$current;
355
+
356
+ if (!shouldBlur) {
357
+ return;
358
+ }
359
+
360
+ (_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.dispatchEvent(new FocusEvent('focusout', {
361
+ bubbles: true
362
+ }));
363
+ setShouldBlur(false);
364
+ }, [shouldBlur]);
365
+ return [input, {
366
+ value,
367
+ onChange: value => {
368
+ setValue(value);
369
+ },
370
+ onBlur: () => {
371
+ setShouldBlur(true);
372
+ }
373
+ }];
374
+ }
375
+
376
+ export { useControlledInput, useFieldList, useFieldset, useForm };
@@ -0,0 +1,4 @@
1
+ export { getFieldElements, parse } from '@conform-to/dom';
2
+ export { useControlledInput, useFieldList, useFieldset, useForm } from './hooks.js';
3
+ import * as helpers from './helpers.js';
4
+ export { helpers as conform };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@conform-to/react",
3
+ "description": "Conform view adapter for react",
4
+ "license": "MIT",
5
+ "version": "0.1.0",
6
+ "main": "index.js",
7
+ "module": "module/index.js",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/edmundhung/conform",
11
+ "directory": "packages/conform-react"
12
+ },
13
+ "author": {
14
+ "name": "Edmund Hung",
15
+ "email": "me@edmund.dev",
16
+ "url": "https://edmund.dev"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/edmundhung/conform/issues"
20
+ },
21
+ "dependencies": {
22
+ "@conform-to/dom": "0.1.0"
23
+ },
24
+ "peerDependencies": {
25
+ "react": ">=16.8"
26
+ },
27
+ "keywords": [
28
+ "constraint-validation",
29
+ "form",
30
+ "form-validation",
31
+ "validation",
32
+ "react"
33
+ ],
34
+ "sideEffects": false
35
+ }