@conform-to/dom 1.1.2 → 1.1.4

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.1.1 / License MIT / Copyright (c) 2024 Edmund Hung
11
+ Version 1.1.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/form.js CHANGED
@@ -51,6 +51,7 @@ function setFieldsValidated(meta, fields) {
51
51
  }
52
52
  }
53
53
  function handleIntent(meta, intent, fields, initialized) {
54
+ var _fields$filter;
54
55
  if (!intent) {
55
56
  setFieldsValidated(meta, fields);
56
57
  return;
@@ -73,7 +74,7 @@ function handleIntent(meta, intent, fields, initialized) {
73
74
  } = intent.payload;
74
75
  var _name2 = formdata.formatName(intent.payload.name, intent.payload.index);
75
76
  if (typeof value !== 'undefined') {
76
- updateValue(meta, _name2 !== null && _name2 !== void 0 ? _name2 : '', submission.serialize(value));
77
+ updateValue(meta, _name2 !== null && _name2 !== void 0 ? _name2 : '', value);
77
78
  }
78
79
  if (typeof validated !== 'undefined') {
79
80
  // Clean up previous validated state
@@ -133,9 +134,10 @@ function handleIntent(meta, intent, fields, initialized) {
133
134
  break;
134
135
  }
135
136
  }
137
+ var validatedFields = (_fields$filter = fields === null || fields === void 0 ? void 0 : fields.filter(name => meta.validated[name])) !== null && _fields$filter !== void 0 ? _fields$filter : [];
136
138
  meta.error = Object.entries(meta.error).reduce((result, _ref3) => {
137
139
  var [name, error] = _ref3;
138
- if (meta.validated[name]) {
140
+ if (meta.validated[name] || validatedFields.some(field => formdata.isPrefix(name, field))) {
139
141
  result[name] = error;
140
142
  }
141
143
  return result;
@@ -145,7 +147,9 @@ function updateValue(meta, name, value) {
145
147
  if (name === '') {
146
148
  meta.initialValue = value;
147
149
  meta.value = value;
148
- meta.key = getDefaultKey(value);
150
+ meta.key = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, getDefaultKey(value)), {}, {
151
+ '': util.generateId()
152
+ });
149
153
  return;
150
154
  }
151
155
  meta.initialValue = util.clone(meta.initialValue);
package/form.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
2
- import { flatten, formatName, getValue, isPlainObject, setValue, normalize, getFormData, getPaths, formatPaths, isPrefix } from './formdata.mjs';
2
+ import { flatten, formatName, getValue, isPlainObject, isPrefix, setValue, normalize, getFormData, getPaths, formatPaths } from './formdata.mjs';
3
3
  import { getFormAction, getFormEncType, getFormMethod, isFieldElement, requestSubmit } from './dom.mjs';
4
4
  import { generateId, clone, invariant } from './util.mjs';
5
5
  import { serialize, setListState, setListValue, setState, INTENT, serializeIntent, root, getSubmissionContext } from './submission.mjs';
@@ -47,6 +47,7 @@ function setFieldsValidated(meta, fields) {
47
47
  }
48
48
  }
49
49
  function handleIntent(meta, intent, fields, initialized) {
50
+ var _fields$filter;
50
51
  if (!intent) {
51
52
  setFieldsValidated(meta, fields);
52
53
  return;
@@ -69,7 +70,7 @@ function handleIntent(meta, intent, fields, initialized) {
69
70
  } = intent.payload;
70
71
  var _name2 = formatName(intent.payload.name, intent.payload.index);
71
72
  if (typeof value !== 'undefined') {
72
- updateValue(meta, _name2 !== null && _name2 !== void 0 ? _name2 : '', serialize(value));
73
+ updateValue(meta, _name2 !== null && _name2 !== void 0 ? _name2 : '', value);
73
74
  }
74
75
  if (typeof validated !== 'undefined') {
75
76
  // Clean up previous validated state
@@ -129,9 +130,10 @@ function handleIntent(meta, intent, fields, initialized) {
129
130
  break;
130
131
  }
131
132
  }
133
+ var validatedFields = (_fields$filter = fields === null || fields === void 0 ? void 0 : fields.filter(name => meta.validated[name])) !== null && _fields$filter !== void 0 ? _fields$filter : [];
132
134
  meta.error = Object.entries(meta.error).reduce((result, _ref3) => {
133
135
  var [name, error] = _ref3;
134
- if (meta.validated[name]) {
136
+ if (meta.validated[name] || validatedFields.some(field => isPrefix(name, field))) {
135
137
  result[name] = error;
136
138
  }
137
139
  return result;
@@ -141,7 +143,9 @@ function updateValue(meta, name, value) {
141
143
  if (name === '') {
142
144
  meta.initialValue = value;
143
145
  meta.value = value;
144
- meta.key = getDefaultKey(value);
146
+ meta.key = _objectSpread2(_objectSpread2({}, getDefaultKey(value)), {}, {
147
+ '': generateId()
148
+ });
145
149
  return;
146
150
  }
147
151
  meta.initialValue = clone(meta.initialValue);
package/formdata.d.ts CHANGED
@@ -55,7 +55,7 @@ export declare function normalize(value: unknown, acceptFile?: boolean): unknown
55
55
  /**
56
56
  * Flatten a tree into a dictionary
57
57
  */
58
- export declare function flatten(data: Record<string | number | symbol, unknown> | Array<unknown> | undefined, options?: {
59
- resolve?: (data: unknown) => unknown | null;
58
+ export declare function flatten(data: unknown, options?: {
59
+ resolve?: (data: unknown) => unknown;
60
60
  prefix?: string;
61
61
  }): Record<string, unknown>;
package/formdata.js CHANGED
@@ -93,7 +93,7 @@ function setValue(target, name, valueFn) {
93
93
  while (pointer != null && ++index < length) {
94
94
  var key = paths[index];
95
95
  var nextKey = paths[index + 1];
96
- var newValue = index != lastIndex ? Object.prototype.hasOwnProperty.call(pointer, key) ? pointer[key] : typeof nextKey === 'number' ? [] : {} : valueFn(pointer[key]);
96
+ var newValue = index != lastIndex ? Object.prototype.hasOwnProperty.call(pointer, key) && pointer[key] !== null ? pointer[key] : typeof nextKey === 'number' ? [] : {} : valueFn(pointer[key]);
97
97
  pointer[key] = newValue;
98
98
  pointer = pointer[key];
99
99
  }
@@ -174,51 +174,29 @@ function normalize(value) {
174
174
  /**
175
175
  * Flatten a tree into a dictionary
176
176
  */
177
- function flatten(data, options) {
177
+ function flatten(data) {
178
178
  var _options$resolve;
179
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
179
180
  var result = {};
180
- var resolve = (_options$resolve = options === null || options === void 0 ? void 0 : options.resolve) !== null && _options$resolve !== void 0 ? _options$resolve : data => data;
181
- function setResult(data, name) {
181
+ var resolve = (_options$resolve = options.resolve) !== null && _options$resolve !== void 0 ? _options$resolve : data => data;
182
+ function process(data, prefix) {
182
183
  var value = normalize(resolve(data));
183
184
  if (typeof value !== 'undefined') {
184
- result[name] = value;
185
+ result[prefix] = value;
185
186
  }
186
- }
187
- function processObject(obj, prefix) {
188
- setResult(obj, prefix);
189
- for (var [key, _value] of Object.entries(obj)) {
190
- var name = prefix ? "".concat(prefix, ".").concat(key) : key;
191
- if (Array.isArray(_value)) {
192
- processArray(_value, name);
193
- } else if (_value && isPlainObject(_value)) {
194
- processObject(_value, name);
195
- } else {
196
- setResult(_value, name);
187
+ if (Array.isArray(data)) {
188
+ for (var i = 0; i < data.length; i++) {
189
+ process(data[i], "".concat(prefix, "[").concat(i, "]"));
197
190
  }
198
- }
199
- }
200
- function processArray(array, prefix) {
201
- setResult(array, prefix);
202
- for (var i = 0; i < array.length; i++) {
203
- var item = array[i];
204
- var name = "".concat(prefix, "[").concat(i, "]");
205
- if (Array.isArray(item)) {
206
- processArray(item, name);
207
- } else if (item && isPlainObject(item)) {
208
- processObject(item, name);
209
- } else {
210
- setResult(item, name);
191
+ } else if (isPlainObject(data)) {
192
+ for (var [key, _value] of Object.entries(data)) {
193
+ process(_value, prefix ? "".concat(prefix, ".").concat(key) : key);
211
194
  }
212
195
  }
213
196
  }
214
197
  if (data) {
215
198
  var _options$prefix;
216
- var prefix = (_options$prefix = options === null || options === void 0 ? void 0 : options.prefix) !== null && _options$prefix !== void 0 ? _options$prefix : '';
217
- if (Array.isArray(data)) {
218
- processArray(data, prefix);
219
- } else {
220
- processObject(data, prefix);
221
- }
199
+ process(data, (_options$prefix = options.prefix) !== null && _options$prefix !== void 0 ? _options$prefix : '');
222
200
  }
223
201
  return result;
224
202
  }
package/formdata.mjs CHANGED
@@ -89,7 +89,7 @@ function setValue(target, name, valueFn) {
89
89
  while (pointer != null && ++index < length) {
90
90
  var key = paths[index];
91
91
  var nextKey = paths[index + 1];
92
- var newValue = index != lastIndex ? Object.prototype.hasOwnProperty.call(pointer, key) ? pointer[key] : typeof nextKey === 'number' ? [] : {} : valueFn(pointer[key]);
92
+ var newValue = index != lastIndex ? Object.prototype.hasOwnProperty.call(pointer, key) && pointer[key] !== null ? pointer[key] : typeof nextKey === 'number' ? [] : {} : valueFn(pointer[key]);
93
93
  pointer[key] = newValue;
94
94
  pointer = pointer[key];
95
95
  }
@@ -170,51 +170,29 @@ function normalize(value) {
170
170
  /**
171
171
  * Flatten a tree into a dictionary
172
172
  */
173
- function flatten(data, options) {
173
+ function flatten(data) {
174
174
  var _options$resolve;
175
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
175
176
  var result = {};
176
- var resolve = (_options$resolve = options === null || options === void 0 ? void 0 : options.resolve) !== null && _options$resolve !== void 0 ? _options$resolve : data => data;
177
- function setResult(data, name) {
177
+ var resolve = (_options$resolve = options.resolve) !== null && _options$resolve !== void 0 ? _options$resolve : data => data;
178
+ function process(data, prefix) {
178
179
  var value = normalize(resolve(data));
179
180
  if (typeof value !== 'undefined') {
180
- result[name] = value;
181
+ result[prefix] = value;
181
182
  }
182
- }
183
- function processObject(obj, prefix) {
184
- setResult(obj, prefix);
185
- for (var [key, _value] of Object.entries(obj)) {
186
- var name = prefix ? "".concat(prefix, ".").concat(key) : key;
187
- if (Array.isArray(_value)) {
188
- processArray(_value, name);
189
- } else if (_value && isPlainObject(_value)) {
190
- processObject(_value, name);
191
- } else {
192
- setResult(_value, name);
183
+ if (Array.isArray(data)) {
184
+ for (var i = 0; i < data.length; i++) {
185
+ process(data[i], "".concat(prefix, "[").concat(i, "]"));
193
186
  }
194
- }
195
- }
196
- function processArray(array, prefix) {
197
- setResult(array, prefix);
198
- for (var i = 0; i < array.length; i++) {
199
- var item = array[i];
200
- var name = "".concat(prefix, "[").concat(i, "]");
201
- if (Array.isArray(item)) {
202
- processArray(item, name);
203
- } else if (item && isPlainObject(item)) {
204
- processObject(item, name);
205
- } else {
206
- setResult(item, name);
187
+ } else if (isPlainObject(data)) {
188
+ for (var [key, _value] of Object.entries(data)) {
189
+ process(_value, prefix ? "".concat(prefix, ".").concat(key) : key);
207
190
  }
208
191
  }
209
192
  }
210
193
  if (data) {
211
194
  var _options$prefix;
212
- var prefix = (_options$prefix = options === null || options === void 0 ? void 0 : options.prefix) !== null && _options$prefix !== void 0 ? _options$prefix : '';
213
- if (Array.isArray(data)) {
214
- processArray(data, prefix);
215
- } else {
216
- processObject(data, prefix);
217
- }
195
+ process(data, (_options$prefix = options.prefix) !== null && _options$prefix !== void 0 ? _options$prefix : '');
218
196
  }
219
197
  return result;
220
198
  }
package/intent.d.ts ADDED
@@ -0,0 +1,82 @@
1
+ import { type Pretty } from './types.js';
2
+ export interface IntentButtonProps {
3
+ name: typeof INTENT;
4
+ value: string;
5
+ formNoValidate?: boolean;
6
+ }
7
+ export type ListIntentPayload<Schema = unknown> = {
8
+ name: string;
9
+ operation: 'insert';
10
+ defaultValue?: Schema;
11
+ index?: number;
12
+ } | {
13
+ name: string;
14
+ operation: 'prepend';
15
+ defaultValue?: Schema;
16
+ } | {
17
+ name: string;
18
+ operation: 'append';
19
+ defaultValue?: Schema;
20
+ } | {
21
+ name: string;
22
+ operation: 'replace';
23
+ defaultValue: Schema;
24
+ index: number;
25
+ } | {
26
+ name: string;
27
+ operation: 'remove';
28
+ index: number;
29
+ } | {
30
+ name: string;
31
+ operation: 'reorder';
32
+ from: number;
33
+ to: number;
34
+ };
35
+ type ExtractListIntentPayload<Operation, Schema = unknown> = Pretty<Omit<Extract<ListIntentPayload<Schema>, {
36
+ operation: Operation;
37
+ }>, 'name' | 'operation'>>;
38
+ type ListIntent<Operation> = {} extends ExtractListIntentPayload<Operation> ? <Schema>(name: string, payload?: ExtractListIntentPayload<Operation, Schema>) => IntentButtonProps : <Schema>(name: string, payload: ExtractListIntentPayload<Operation, Schema>) => IntentButtonProps;
39
+ /**
40
+ * Helpers to configure an intent button for modifying a list
41
+ *
42
+ * @see https://conform.guide/api/react#list
43
+ */
44
+ export declare const list: {
45
+ /**
46
+ * @deprecated You can use `insert` without specifying an index instead
47
+ */
48
+ append: ListIntent<'append'>;
49
+ /**
50
+ * @deprecated You can use `insert` with zero index instead
51
+ */
52
+ prepend: ListIntent<'prepend'>;
53
+ insert: ListIntent<'insert'>;
54
+ replace: ListIntent<'replace'>;
55
+ remove: ListIntent<'remove'>;
56
+ reorder: ListIntent<'reorder'>;
57
+ };
58
+ export declare const INTENT = "__intent__";
59
+ /**
60
+ * Returns the intent from the form data or search params.
61
+ * It throws an error if multiple intent is set.
62
+ */
63
+ export declare function getIntent(payload: FormData | URLSearchParams): string;
64
+ /**
65
+ * Returns the properties required to configure an intent button for validation
66
+ *
67
+ * @see https://conform.guide/api/react#validate
68
+ */
69
+ export declare function validate(field: string): IntentButtonProps;
70
+ export declare function requestIntent(form: HTMLFormElement | null | undefined, buttonProps: {
71
+ value: string;
72
+ formNoValidate?: boolean;
73
+ }): void;
74
+ export declare function parseIntent<Schema>(intent: string): {
75
+ type: 'validate';
76
+ payload: string;
77
+ } | {
78
+ type: 'list';
79
+ payload: ListIntentPayload<Schema>;
80
+ } | null;
81
+ export declare function updateList<Schema>(list: Array<Schema>, payload: ListIntentPayload<Schema>): Array<Schema>;
82
+ export {};
package/intent.js ADDED
@@ -0,0 +1,136 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
6
+ var dom = require('./dom.js');
7
+
8
+ /**
9
+ * Helpers to configure an intent button for modifying a list
10
+ *
11
+ * @see https://conform.guide/api/react#list
12
+ */
13
+ var list = new Proxy({}, {
14
+ get(_target, operation) {
15
+ return function (name) {
16
+ var payload = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
17
+ return {
18
+ name: INTENT,
19
+ value: "list/".concat(JSON.stringify(_rollupPluginBabelHelpers.objectSpread2({
20
+ name,
21
+ operation
22
+ }, payload))),
23
+ formNoValidate: true
24
+ };
25
+ };
26
+ }
27
+ });
28
+ var INTENT = '__intent__';
29
+
30
+ /**
31
+ * Returns the intent from the form data or search params.
32
+ * It throws an error if multiple intent is set.
33
+ */
34
+ function getIntent(payload) {
35
+ if (!payload.has(INTENT)) {
36
+ return 'submit';
37
+ }
38
+ var [intent, secondIntent, ...rest] = payload.getAll(INTENT);
39
+
40
+ // The submitter value is included in the formData directly on Safari 15.6.
41
+ // This causes the intent to be duplicated in the payload.
42
+ // We will ignore the second intent if it is the same as the first one.
43
+ if (typeof intent !== 'string' || secondIntent && intent !== secondIntent || rest.length > 0) {
44
+ throw new Error('The intent could only be set on a button');
45
+ }
46
+ return intent;
47
+ }
48
+
49
+ /**
50
+ * Returns the properties required to configure an intent button for validation
51
+ *
52
+ * @see https://conform.guide/api/react#validate
53
+ */
54
+ function validate(field) {
55
+ return {
56
+ name: INTENT,
57
+ value: "validate/".concat(field),
58
+ formNoValidate: true
59
+ };
60
+ }
61
+ function requestIntent(form, buttonProps) {
62
+ if (!form) {
63
+ // eslint-disable-next-line no-console
64
+ console.warn('No form element is provided');
65
+ return;
66
+ }
67
+ var submitter = dom.createSubmitter({
68
+ name: INTENT,
69
+ value: buttonProps.value,
70
+ hidden: true,
71
+ formNoValidate: buttonProps.formNoValidate
72
+ });
73
+ dom.requestSubmit(form, submitter);
74
+ }
75
+ function parseIntent(intent) {
76
+ var seperatorIndex = intent.indexOf('/');
77
+ if (seperatorIndex > -1) {
78
+ var type = intent.slice(0, seperatorIndex);
79
+ var _payload = intent.slice(seperatorIndex + 1);
80
+ if (typeof _payload !== 'undefined') {
81
+ try {
82
+ switch (type) {
83
+ case 'validate':
84
+ return {
85
+ type,
86
+ payload: _payload
87
+ };
88
+ case 'list':
89
+ return {
90
+ type,
91
+ payload: JSON.parse(_payload)
92
+ };
93
+ }
94
+ } catch (error) {
95
+ throw new Error("Failed parsing intent: ".concat(intent), {
96
+ cause: error
97
+ });
98
+ }
99
+ }
100
+ }
101
+ return null;
102
+ }
103
+ function updateList(list, payload) {
104
+ var _payload$index;
105
+ switch (payload.operation) {
106
+ case 'prepend':
107
+ list.unshift(payload.defaultValue);
108
+ break;
109
+ case 'append':
110
+ list.push(payload.defaultValue);
111
+ break;
112
+ case 'insert':
113
+ list.splice((_payload$index = payload.index) !== null && _payload$index !== void 0 ? _payload$index : list.length, 0, payload.defaultValue);
114
+ break;
115
+ case 'replace':
116
+ list.splice(payload.index, 1, payload.defaultValue);
117
+ break;
118
+ case 'remove':
119
+ list.splice(payload.index, 1);
120
+ break;
121
+ case 'reorder':
122
+ list.splice(payload.to, 0, ...list.splice(payload.from, 1));
123
+ break;
124
+ default:
125
+ throw new Error('Unknown list intent received');
126
+ }
127
+ return list;
128
+ }
129
+
130
+ exports.INTENT = INTENT;
131
+ exports.getIntent = getIntent;
132
+ exports.list = list;
133
+ exports.parseIntent = parseIntent;
134
+ exports.requestIntent = requestIntent;
135
+ exports.updateList = updateList;
136
+ exports.validate = validate;
package/intent.mjs ADDED
@@ -0,0 +1,126 @@
1
+ import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
2
+ import { createSubmitter, requestSubmit } from './dom.mjs';
3
+
4
+ /**
5
+ * Helpers to configure an intent button for modifying a list
6
+ *
7
+ * @see https://conform.guide/api/react#list
8
+ */
9
+ var list = new Proxy({}, {
10
+ get(_target, operation) {
11
+ return function (name) {
12
+ var payload = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
13
+ return {
14
+ name: INTENT,
15
+ value: "list/".concat(JSON.stringify(_objectSpread2({
16
+ name,
17
+ operation
18
+ }, payload))),
19
+ formNoValidate: true
20
+ };
21
+ };
22
+ }
23
+ });
24
+ var INTENT = '__intent__';
25
+
26
+ /**
27
+ * Returns the intent from the form data or search params.
28
+ * It throws an error if multiple intent is set.
29
+ */
30
+ function getIntent(payload) {
31
+ if (!payload.has(INTENT)) {
32
+ return 'submit';
33
+ }
34
+ var [intent, secondIntent, ...rest] = payload.getAll(INTENT);
35
+
36
+ // The submitter value is included in the formData directly on Safari 15.6.
37
+ // This causes the intent to be duplicated in the payload.
38
+ // We will ignore the second intent if it is the same as the first one.
39
+ if (typeof intent !== 'string' || secondIntent && intent !== secondIntent || rest.length > 0) {
40
+ throw new Error('The intent could only be set on a button');
41
+ }
42
+ return intent;
43
+ }
44
+
45
+ /**
46
+ * Returns the properties required to configure an intent button for validation
47
+ *
48
+ * @see https://conform.guide/api/react#validate
49
+ */
50
+ function validate(field) {
51
+ return {
52
+ name: INTENT,
53
+ value: "validate/".concat(field),
54
+ formNoValidate: true
55
+ };
56
+ }
57
+ function requestIntent(form, buttonProps) {
58
+ if (!form) {
59
+ // eslint-disable-next-line no-console
60
+ console.warn('No form element is provided');
61
+ return;
62
+ }
63
+ var submitter = createSubmitter({
64
+ name: INTENT,
65
+ value: buttonProps.value,
66
+ hidden: true,
67
+ formNoValidate: buttonProps.formNoValidate
68
+ });
69
+ requestSubmit(form, submitter);
70
+ }
71
+ function parseIntent(intent) {
72
+ var seperatorIndex = intent.indexOf('/');
73
+ if (seperatorIndex > -1) {
74
+ var type = intent.slice(0, seperatorIndex);
75
+ var _payload = intent.slice(seperatorIndex + 1);
76
+ if (typeof _payload !== 'undefined') {
77
+ try {
78
+ switch (type) {
79
+ case 'validate':
80
+ return {
81
+ type,
82
+ payload: _payload
83
+ };
84
+ case 'list':
85
+ return {
86
+ type,
87
+ payload: JSON.parse(_payload)
88
+ };
89
+ }
90
+ } catch (error) {
91
+ throw new Error("Failed parsing intent: ".concat(intent), {
92
+ cause: error
93
+ });
94
+ }
95
+ }
96
+ }
97
+ return null;
98
+ }
99
+ function updateList(list, payload) {
100
+ var _payload$index;
101
+ switch (payload.operation) {
102
+ case 'prepend':
103
+ list.unshift(payload.defaultValue);
104
+ break;
105
+ case 'append':
106
+ list.push(payload.defaultValue);
107
+ break;
108
+ case 'insert':
109
+ list.splice((_payload$index = payload.index) !== null && _payload$index !== void 0 ? _payload$index : list.length, 0, payload.defaultValue);
110
+ break;
111
+ case 'replace':
112
+ list.splice(payload.index, 1, payload.defaultValue);
113
+ break;
114
+ case 'remove':
115
+ list.splice(payload.index, 1);
116
+ break;
117
+ case 'reorder':
118
+ list.splice(payload.to, 0, ...list.splice(payload.from, 1));
119
+ break;
120
+ default:
121
+ throw new Error('Unknown list intent received');
122
+ }
123
+ return list;
124
+ }
125
+
126
+ export { INTENT, getIntent, list, parseIntent, requestIntent, updateList, validate };
package/package.json CHANGED
@@ -1,42 +1,59 @@
1
1
  {
2
- "name": "@conform-to/dom",
3
- "description": "A set of opinionated helpers built on top of the Constraint Validation API",
4
- "homepage": "https://conform.guide",
5
- "license": "MIT",
6
- "version": "1.1.2",
7
- "main": "index.js",
8
- "module": "index.mjs",
9
- "types": "index.d.ts",
10
- "exports": {
11
- ".": {
12
- "types": "./index.d.ts",
13
- "module": "./index.mjs",
14
- "import": "./index.mjs",
15
- "require": "./index.js",
16
- "default": "./index.mjs"
17
- }
18
- },
19
- "repository": {
20
- "type": "git",
21
- "url": "https://github.com/edmundhung/conform",
22
- "directory": "packages/conform-dom"
23
- },
24
- "author": {
25
- "name": "Edmund Hung",
26
- "email": "me@edmund.dev",
27
- "url": "https://edmund.dev"
28
- },
29
- "bugs": {
30
- "url": "https://github.com/edmundhung/conform/issues"
31
- },
32
- "keywords": [
33
- "constraint-validation",
34
- "form",
35
- "form-validation",
36
- "html",
37
- "progressive-enhancement",
38
- "validation",
39
- "dom"
40
- ],
41
- "sideEffects": false
42
- }
2
+ "name": "@conform-to/dom",
3
+ "description": "A set of opinionated helpers built on top of the Constraint Validation API",
4
+ "homepage": "https://conform.guide",
5
+ "license": "MIT",
6
+ "version": "1.1.4",
7
+ "main": "index.js",
8
+ "module": "index.mjs",
9
+ "types": "index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./index.d.ts",
13
+ "module": "./index.mjs",
14
+ "import": "./index.mjs",
15
+ "require": "./index.js",
16
+ "default": "./index.mjs"
17
+ }
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/edmundhung/conform",
22
+ "directory": "packages/conform-dom"
23
+ },
24
+ "author": {
25
+ "name": "Edmund Hung",
26
+ "email": "me@edmund.dev",
27
+ "url": "https://edmund.dev"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/edmundhung/conform/issues"
31
+ },
32
+ "keywords": [
33
+ "constraint-validation",
34
+ "form",
35
+ "form-validation",
36
+ "html",
37
+ "progressive-enhancement",
38
+ "validation",
39
+ "dom"
40
+ ],
41
+ "sideEffects": false,
42
+ "devDependencies": {
43
+ "@babel/core": "^7.17.8",
44
+ "@babel/preset-env": "^7.20.2",
45
+ "@babel/preset-typescript": "^7.20.2",
46
+ "@rollup/plugin-babel": "^5.3.1",
47
+ "@rollup/plugin-node-resolve": "^13.3.0",
48
+ "rollup-plugin-copy": "^3.4.0",
49
+ "rollup": "^2.79.1"
50
+ },
51
+ "scripts": {
52
+ "build:js": "rollup -c",
53
+ "build:ts": "tsc",
54
+ "build": "pnpm run \"/^build:.*/\"",
55
+ "dev:js": "pnpm run build:js --watch",
56
+ "dev:ts": "pnpm run build:ts --watch",
57
+ "dev": "pnpm run \"/^dev:.*/\""
58
+ }
59
+ }
package/parse.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ export type Submission<Schema = any> = {
2
+ intent: string;
3
+ payload: Record<string, unknown>;
4
+ error: Record<string, string[]>;
5
+ value?: Schema | null;
6
+ };
7
+ export declare const VALIDATION_UNDEFINED = "__undefined__";
8
+ export declare const VALIDATION_SKIPPED = "__skipped__";
9
+ export declare function parse(payload: FormData | URLSearchParams): Submission;
10
+ export declare function parse<Schema>(payload: FormData | URLSearchParams, options?: {
11
+ resolve?: (payload: Record<string, any>, intent: string) => {
12
+ value?: Schema;
13
+ error?: Record<string, string[]>;
14
+ };
15
+ }): Submission<Schema>;
16
+ export declare function parse<Schema>(payload: FormData | URLSearchParams, options?: {
17
+ resolve?: (payload: Record<string, any>, intent: string) => Promise<{
18
+ value?: Schema;
19
+ error?: Record<string, string[]>;
20
+ }>;
21
+ }): Promise<Submission<Schema>>;
22
+ export declare function parse<Schema>(payload: FormData | URLSearchParams, options?: {
23
+ resolve?: (payload: Record<string, any>, intent: string) => {
24
+ value?: Schema;
25
+ error?: Record<string, string[]>;
26
+ } | Promise<{
27
+ value?: Schema;
28
+ error?: Record<string, string[]>;
29
+ }>;
30
+ }): Submission<Schema> | Promise<Submission<Schema>>;
package/parse.js ADDED
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
6
+ var formdata = require('./formdata.js');
7
+ var intent = require('./intent.js');
8
+
9
+ var VALIDATION_UNDEFINED = '__undefined__';
10
+ var VALIDATION_SKIPPED = '__skipped__';
11
+ function parse(payload, options) {
12
+ var submission = {
13
+ intent: intent.getIntent(payload),
14
+ payload: formdata.resolve(payload, {
15
+ ignoreKeys: [intent.INTENT]
16
+ }),
17
+ error: {}
18
+ };
19
+ var intent$1 = intent.parseIntent(submission.intent);
20
+ if (intent$1 && intent$1.type === 'list') {
21
+ formdata.setValue(submission.payload, intent$1.payload.name, list => {
22
+ if (typeof list !== 'undefined' && !Array.isArray(list)) {
23
+ throw new Error('The list intent can only be applied to a list');
24
+ }
25
+ return intent.updateList(list !== null && list !== void 0 ? list : [], intent$1.payload);
26
+ });
27
+ }
28
+ if (typeof (options === null || options === void 0 ? void 0 : options.resolve) === 'undefined') {
29
+ return submission;
30
+ }
31
+ var result = options.resolve(submission.payload, submission.intent);
32
+ var mergeResolveResult = resolved => {
33
+ return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, submission), resolved);
34
+ };
35
+ if (result instanceof Promise) {
36
+ return result.then(mergeResolveResult);
37
+ }
38
+ return mergeResolveResult(result);
39
+ }
40
+
41
+ exports.VALIDATION_SKIPPED = VALIDATION_SKIPPED;
42
+ exports.VALIDATION_UNDEFINED = VALIDATION_UNDEFINED;
43
+ exports.parse = parse;
package/parse.mjs ADDED
@@ -0,0 +1,37 @@
1
+ import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
2
+ import { resolve, setValue } from './formdata.mjs';
3
+ import { getIntent, parseIntent, updateList, INTENT } from './intent.mjs';
4
+
5
+ var VALIDATION_UNDEFINED = '__undefined__';
6
+ var VALIDATION_SKIPPED = '__skipped__';
7
+ function parse(payload, options) {
8
+ var submission = {
9
+ intent: getIntent(payload),
10
+ payload: resolve(payload, {
11
+ ignoreKeys: [INTENT]
12
+ }),
13
+ error: {}
14
+ };
15
+ var intent = parseIntent(submission.intent);
16
+ if (intent && intent.type === 'list') {
17
+ setValue(submission.payload, intent.payload.name, list => {
18
+ if (typeof list !== 'undefined' && !Array.isArray(list)) {
19
+ throw new Error('The list intent can only be applied to a list');
20
+ }
21
+ return updateList(list !== null && list !== void 0 ? list : [], intent.payload);
22
+ });
23
+ }
24
+ if (typeof (options === null || options === void 0 ? void 0 : options.resolve) === 'undefined') {
25
+ return submission;
26
+ }
27
+ var result = options.resolve(submission.payload, submission.intent);
28
+ var mergeResolveResult = resolved => {
29
+ return _objectSpread2(_objectSpread2({}, submission), resolved);
30
+ };
31
+ if (result instanceof Promise) {
32
+ return result.then(mergeResolveResult);
33
+ }
34
+ return mergeResolveResult(result);
35
+ }
36
+
37
+ export { VALIDATION_SKIPPED, VALIDATION_UNDEFINED, parse };
@@ -0,0 +1,100 @@
1
+ import path from 'node:path';
2
+ import babel from '@rollup/plugin-babel';
3
+ import nodeResolve from '@rollup/plugin-node-resolve';
4
+ import copy from 'rollup-plugin-copy';
5
+
6
+ /** @returns {import("rollup").RollupOptions[]} */
7
+ function configurePackage() {
8
+ let sourceDir = '.';
9
+ let outputDir = sourceDir;
10
+
11
+ /** @type {import("rollup").RollupOptions} */
12
+ let ESM = {
13
+ external(id) {
14
+ return !id.startsWith('.') && !path.isAbsolute(id);
15
+ },
16
+ input: `${sourceDir}/index.ts`,
17
+ output: {
18
+ dir: outputDir,
19
+ format: 'esm',
20
+ preserveModules: true,
21
+ entryFileNames: '[name].mjs',
22
+ },
23
+ plugins: [
24
+ babel({
25
+ babelrc: false,
26
+ configFile: false,
27
+ presets: [
28
+ [
29
+ '@babel/preset-env',
30
+ {
31
+ targets: {
32
+ node: '16',
33
+ esmodules: true,
34
+ },
35
+ },
36
+ ],
37
+ '@babel/preset-typescript',
38
+ ],
39
+ plugins: [],
40
+ babelHelpers: 'bundled',
41
+ exclude: /node_modules/,
42
+ extensions: ['.ts', '.tsx'],
43
+ }),
44
+ nodeResolve({
45
+ extensions: ['.ts', '.tsx'],
46
+ }),
47
+ copy({
48
+ targets: [
49
+ { src: `../../README`, dest: sourceDir },
50
+ { src: `../../LICENSE`, dest: sourceDir },
51
+ ],
52
+ }),
53
+ ],
54
+ };
55
+
56
+ /** @type {import("rollup").RollupOptions} */
57
+ let CJS = {
58
+ external(id) {
59
+ return !id.startsWith('.') && !path.isAbsolute(id);
60
+ },
61
+ input: `${sourceDir}/index.ts`,
62
+ output: {
63
+ dir: outputDir,
64
+ format: 'cjs',
65
+ preserveModules: true,
66
+ exports: 'auto',
67
+ },
68
+ plugins: [
69
+ babel({
70
+ babelrc: false,
71
+ configFile: false,
72
+ presets: [
73
+ [
74
+ '@babel/preset-env',
75
+ {
76
+ targets: {
77
+ node: '16',
78
+ esmodules: true,
79
+ },
80
+ },
81
+ ],
82
+ '@babel/preset-typescript',
83
+ ],
84
+ plugins: [],
85
+ babelHelpers: 'bundled',
86
+ exclude: /node_modules/,
87
+ extensions: ['.ts', '.tsx'],
88
+ }),
89
+ nodeResolve({
90
+ extensions: ['.ts', '.tsx'],
91
+ }),
92
+ ],
93
+ };
94
+
95
+ return [ESM, CJS];
96
+ }
97
+
98
+ export default function rollup() {
99
+ return configurePackage();
100
+ }
package/submission.d.ts CHANGED
@@ -84,7 +84,7 @@ export type ResetIntent<Schema = any> = {
84
84
  index: Schema extends Array<unknown> ? number : never;
85
85
  };
86
86
  };
87
- export type UpdateIntent<Schema = unknown> = {
87
+ export type UpdateIntent<Schema = any> = {
88
88
  type: 'update';
89
89
  payload: {
90
90
  name?: FieldName<Schema>;
@@ -94,18 +94,18 @@ export type UpdateIntent<Schema = unknown> = {
94
94
  } | {
95
95
  name: FieldName<Schema>;
96
96
  index: Schema extends Array<unknown> ? number : never;
97
- value?: NonNullable<DefaultValue<Schema extends Array<infer Item> ? Item : unknown>>;
97
+ value?: Schema extends Array<infer Item> ? NonNullable<DefaultValue<Item>> : never;
98
98
  validated?: boolean;
99
99
  };
100
100
  };
101
- export type RemoveIntent<Schema extends Array<any> = any> = {
101
+ export type RemoveIntent<Schema = any> = {
102
102
  type: 'remove';
103
103
  payload: {
104
104
  name: FieldName<Schema>;
105
105
  index: number;
106
106
  };
107
107
  };
108
- export type InsertIntent<Schema extends Array<any> = any> = {
108
+ export type InsertIntent<Schema = any> = {
109
109
  type: 'insert';
110
110
  payload: {
111
111
  name: FieldName<Schema>;
@@ -113,7 +113,7 @@ export type InsertIntent<Schema extends Array<any> = any> = {
113
113
  index?: number;
114
114
  };
115
115
  };
116
- export type ReorderIntent<Schema extends Array<any> = any> = {
116
+ export type ReorderIntent<Schema = any> = {
117
117
  type: 'reorder';
118
118
  payload: {
119
119
  name: FieldName<Schema>;
@@ -121,7 +121,7 @@ export type ReorderIntent<Schema extends Array<any> = any> = {
121
121
  to: number;
122
122
  };
123
123
  };
124
- export type Intent<Schema = unknown> = ValidateIntent<Schema> | ResetIntent<Schema> | UpdateIntent<Schema> | ReorderIntent<Schema extends Array<any> ? Schema : any> | RemoveIntent<Schema extends Array<any> ? Schema : any> | InsertIntent<Schema extends Array<any> ? Schema : any>;
124
+ export type Intent<Schema = any> = ValidateIntent<Schema> | ResetIntent<Schema> | UpdateIntent<Schema> | ReorderIntent<Schema> | RemoveIntent<Schema> | InsertIntent<Schema>;
125
125
  export declare function getIntent(serializedIntent: string | null | undefined): Intent | null;
126
126
  export declare function serializeIntent<Schema>(intent: Intent<Schema>): string;
127
127
  export declare function updateList(list: unknown, intent: InsertIntent | RemoveIntent | ReorderIntent): void;
@@ -132,4 +132,4 @@ export declare function setListValue(data: Record<string, unknown>, intent: Inse
132
132
  export declare const root: unique symbol;
133
133
  export declare function setState(state: Record<string, unknown>, name: string, valueFn: (value: unknown) => unknown): void;
134
134
  export declare function setListState(state: Record<string, unknown>, intent: InsertIntent | RemoveIntent | ReorderIntent, getDefaultValue?: (defaultValue: any) => any): void;
135
- export declare function serialize<Schema>(defaultValue: DefaultValue<Schema>): FormValue<Schema>;
135
+ export declare function serialize<Schema>(defaultValue: Schema): FormValue<Schema>;
package/submission.js CHANGED
@@ -55,12 +55,11 @@ function parse(payload, options) {
55
55
  case 'update':
56
56
  {
57
57
  var name = formdata.formatName(intent.payload.name, intent.payload.index);
58
- var _value = serialize(intent.payload.value);
59
- if (typeof _value !== 'undefined') {
58
+ var _value = intent.payload.value;
59
+ if (typeof intent.payload.value !== 'undefined') {
60
60
  if (name) {
61
61
  formdata.setValue(context.payload, name, () => _value);
62
62
  } else {
63
- // @ts-expect-error FIXME - it must be an object if there is no name
64
63
  context.payload = _value;
65
64
  }
66
65
  }
@@ -77,15 +76,6 @@ function parse(payload, options) {
77
76
  break;
78
77
  }
79
78
  case 'insert':
80
- {
81
- setListValue(context.payload, {
82
- type: intent.type,
83
- payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, intent.payload), {}, {
84
- defaultValue: serialize(intent.payload.defaultValue)
85
- })
86
- });
87
- break;
88
- }
89
79
  case 'remove':
90
80
  case 'reorder':
91
81
  {
@@ -166,7 +156,24 @@ function getIntent(serializedIntent) {
166
156
  return control;
167
157
  }
168
158
  function serializeIntent(intent) {
169
- return JSON.stringify(intent);
159
+ switch (intent.type) {
160
+ case 'insert':
161
+ return JSON.stringify({
162
+ type: intent.type,
163
+ payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, intent.payload), {}, {
164
+ defaultValue: serialize(intent.payload.defaultValue)
165
+ })
166
+ });
167
+ case 'update':
168
+ return JSON.stringify({
169
+ type: intent.type,
170
+ payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, intent.payload), {}, {
171
+ value: serialize(intent.payload.value)
172
+ })
173
+ });
174
+ default:
175
+ return JSON.stringify(intent);
176
+ }
170
177
  }
171
178
  function updateList(list, intent) {
172
179
  var _intent$payload$index;
@@ -225,9 +232,7 @@ function setState(state, name, valueFn) {
225
232
  _loop2();
226
233
  }
227
234
  var result = valueFn(formdata.getValue(target, name));
228
- Object.assign(state,
229
- // @ts-expect-error FIXME flatten should be more flexible
230
- formdata.flatten(result, {
235
+ Object.assign(state, formdata.flatten(result, {
231
236
  resolve(data) {
232
237
  if (formdata.isPlainObject(data) || Array.isArray(data)) {
233
238
  var _data$root;
@@ -263,22 +268,19 @@ function serialize(defaultValue) {
263
268
  // @ts-expect-error FIXME
264
269
  return Object.entries(defaultValue).reduce((result, _ref) => {
265
270
  var [key, value] = _ref;
266
- // @ts-ignore-error FIXME
267
271
  result[key] = serialize(value);
268
272
  return result;
269
273
  }, {});
270
274
  } else if (Array.isArray(defaultValue)) {
271
275
  // @ts-expect-error FIXME
272
276
  return defaultValue.map(serialize);
273
- } else if (
274
- // @ts-ignore-error FIXME
275
- defaultValue instanceof Date) {
277
+ } else if (defaultValue instanceof Date) {
276
278
  // @ts-expect-error FIXME
277
279
  return defaultValue.toISOString();
278
280
  } else if (typeof defaultValue === 'boolean') {
279
281
  // @ts-expect-error FIXME
280
282
  return defaultValue ? 'on' : undefined;
281
- } else if (typeof defaultValue === 'number') {
283
+ } else if (typeof defaultValue === 'number' || typeof defaultValue === 'bigint') {
282
284
  // @ts-expect-error FIXME
283
285
  return defaultValue.toString();
284
286
  } else {
package/submission.mjs CHANGED
@@ -51,12 +51,11 @@ function parse(payload, options) {
51
51
  case 'update':
52
52
  {
53
53
  var name = formatName(intent.payload.name, intent.payload.index);
54
- var _value = serialize(intent.payload.value);
55
- if (typeof _value !== 'undefined') {
54
+ var _value = intent.payload.value;
55
+ if (typeof intent.payload.value !== 'undefined') {
56
56
  if (name) {
57
57
  setValue(context.payload, name, () => _value);
58
58
  } else {
59
- // @ts-expect-error FIXME - it must be an object if there is no name
60
59
  context.payload = _value;
61
60
  }
62
61
  }
@@ -73,15 +72,6 @@ function parse(payload, options) {
73
72
  break;
74
73
  }
75
74
  case 'insert':
76
- {
77
- setListValue(context.payload, {
78
- type: intent.type,
79
- payload: _objectSpread2(_objectSpread2({}, intent.payload), {}, {
80
- defaultValue: serialize(intent.payload.defaultValue)
81
- })
82
- });
83
- break;
84
- }
85
75
  case 'remove':
86
76
  case 'reorder':
87
77
  {
@@ -162,7 +152,24 @@ function getIntent(serializedIntent) {
162
152
  return control;
163
153
  }
164
154
  function serializeIntent(intent) {
165
- return JSON.stringify(intent);
155
+ switch (intent.type) {
156
+ case 'insert':
157
+ return JSON.stringify({
158
+ type: intent.type,
159
+ payload: _objectSpread2(_objectSpread2({}, intent.payload), {}, {
160
+ defaultValue: serialize(intent.payload.defaultValue)
161
+ })
162
+ });
163
+ case 'update':
164
+ return JSON.stringify({
165
+ type: intent.type,
166
+ payload: _objectSpread2(_objectSpread2({}, intent.payload), {}, {
167
+ value: serialize(intent.payload.value)
168
+ })
169
+ });
170
+ default:
171
+ return JSON.stringify(intent);
172
+ }
166
173
  }
167
174
  function updateList(list, intent) {
168
175
  var _intent$payload$index;
@@ -221,9 +228,7 @@ function setState(state, name, valueFn) {
221
228
  _loop2();
222
229
  }
223
230
  var result = valueFn(getValue(target, name));
224
- Object.assign(state,
225
- // @ts-expect-error FIXME flatten should be more flexible
226
- flatten(result, {
231
+ Object.assign(state, flatten(result, {
227
232
  resolve(data) {
228
233
  if (isPlainObject(data) || Array.isArray(data)) {
229
234
  var _data$root;
@@ -259,22 +264,19 @@ function serialize(defaultValue) {
259
264
  // @ts-expect-error FIXME
260
265
  return Object.entries(defaultValue).reduce((result, _ref) => {
261
266
  var [key, value] = _ref;
262
- // @ts-ignore-error FIXME
263
267
  result[key] = serialize(value);
264
268
  return result;
265
269
  }, {});
266
270
  } else if (Array.isArray(defaultValue)) {
267
271
  // @ts-expect-error FIXME
268
272
  return defaultValue.map(serialize);
269
- } else if (
270
- // @ts-ignore-error FIXME
271
- defaultValue instanceof Date) {
273
+ } else if (defaultValue instanceof Date) {
272
274
  // @ts-expect-error FIXME
273
275
  return defaultValue.toISOString();
274
276
  } else if (typeof defaultValue === 'boolean') {
275
277
  // @ts-expect-error FIXME
276
278
  return defaultValue ? 'on' : undefined;
277
- } else if (typeof defaultValue === 'number') {
279
+ } else if (typeof defaultValue === 'number' || typeof defaultValue === 'bigint') {
278
280
  // @ts-expect-error FIXME
279
281
  return defaultValue.toString();
280
282
  } else {
package/types.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ export type Pretty<T> = {
2
+ [K in keyof T]: T[K];
3
+ } & {};
4
+ export type FieldConstraint<Schema = any> = {
5
+ required?: boolean;
6
+ minLength?: number;
7
+ maxLength?: number;
8
+ min?: Schema extends number ? number : string | number;
9
+ max?: Schema extends number ? number : string | number;
10
+ step?: Schema extends number ? number : string | number;
11
+ multiple?: boolean;
12
+ pattern?: string;
13
+ };
14
+ export type KeysOf<T> = T extends any ? keyof T : never;
15
+ export type ResolveType<T, K extends KeysOf<T>> = T extends {
16
+ [k in K]?: any;
17
+ } ? T[K] : undefined;
18
+ export type FieldsetConstraint<Schema extends Record<string, any> | undefined> = {
19
+ [Key in KeysOf<Schema>]?: FieldConstraint<ResolveType<Schema, Key>>;
20
+ };