@conform-to/dom 0.3.0 → 0.4.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.
Files changed (4) hide show
  1. package/index.d.ts +31 -39
  2. package/index.js +164 -155
  3. package/module/index.js +157 -147
  4. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -3,18 +3,12 @@ export declare type FieldElement = HTMLInputElement | HTMLSelectElement | HTMLTe
3
3
  export interface FieldConfig<Schema = unknown> extends FieldConstraint {
4
4
  name: string;
5
5
  defaultValue?: FieldValue<Schema>;
6
- initialError?: FieldError<Schema>['details'];
6
+ initialError?: Array<[string, string]>;
7
7
  form?: string;
8
8
  }
9
9
  export declare type FieldValue<Schema> = Schema extends Primitive | File ? string : Schema extends Array<infer InnerType> ? Array<FieldValue<InnerType>> : Schema extends Record<string, any> ? {
10
10
  [Key in keyof Schema]?: FieldValue<Schema[Key]>;
11
11
  } : unknown;
12
- export interface FieldError<Schema> {
13
- message?: string;
14
- details?: Schema extends Primitive | File ? never : Schema extends Array<infer InnerType> ? Array<FieldError<InnerType>> : Schema extends Record<string, any> ? {
15
- [Key in keyof Schema]?: FieldError<Schema[Key]>;
16
- } : unknown;
17
- }
18
12
  export declare type FieldConstraint = {
19
13
  required?: boolean;
20
14
  minLength?: number;
@@ -28,69 +22,67 @@ export declare type FieldConstraint = {
28
22
  export declare type FieldsetConstraint<Schema extends Record<string, any>> = {
29
23
  [Key in keyof Schema]?: FieldConstraint;
30
24
  };
31
- export declare type Schema<Shape extends Record<string, any>, Source> = {
32
- source: Source;
33
- constraint: FieldsetConstraint<Shape>;
34
- validate: (element: HTMLFormElement, submitter?: HTMLInputElement | HTMLButtonElement | null) => void;
35
- parse: (payload: FormData | URLSearchParams) => Submission<Shape>;
36
- };
37
- export interface FormState<Schema extends Record<string, any>> {
25
+ export declare type FormState<Schema = unknown> = {
26
+ scope: string[];
38
27
  value: FieldValue<Schema>;
39
- error: FieldError<Schema>;
40
- }
41
- export declare type Submission<T extends Record<string, unknown>> = {
42
- state: 'modified';
43
- form: FormState<T>;
44
- } | {
45
- state: 'rejected';
46
- form: FormState<T>;
47
- } | {
48
- state: 'accepted';
49
- data: T;
50
- form: FormState<T>;
28
+ error: Array<[string, string]>;
51
29
  };
30
+ export declare type Submission<Schema = unknown> = FormState<Schema> & ({
31
+ type?: undefined;
32
+ } | {
33
+ type: string;
34
+ data: string;
35
+ });
52
36
  export declare function isFieldElement(element: unknown): element is FieldElement;
53
- export declare function getPaths(name?: string): Array<string | number>;
37
+ export declare function getPaths(name: string): Array<string | number>;
54
38
  export declare function getFormData(form: HTMLFormElement, submitter?: HTMLInputElement | HTMLButtonElement | null): FormData;
55
39
  export declare function getName(paths: Array<string | number>): string;
56
- export declare function getKey(fieldName: string, fieldsetName?: string): string | null;
57
- export declare function setFormError(form: HTMLFormElement, errors: Array<[string, string]>): void;
40
+ export declare function hasError(error: Array<[string, string]>, name: string): boolean;
41
+ export declare function reportValidity(form: HTMLFormElement, state: FormState): boolean;
58
42
  export declare function setValue<T>(target: any, paths: Array<string | number>, valueFn: (prev?: T) => T): void;
59
- export declare function flatten(data: unknown, prefix?: string): Array<[string, FormDataEntryValue]>;
60
- export declare function unflatten(entries: Array<[string, FormDataEntryValue]> | Iterable<[string, FormDataEntryValue]>): Record<string, unknown>;
61
- export declare function createSubmission(payload: FormData | URLSearchParams): Submission<Record<string, unknown>>;
62
- export declare function createValidate(handler: (field: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement, formData: FormData) => void): (form: HTMLFormElement, submitter?: HTMLInputElement | HTMLButtonElement | null) => void;
43
+ export declare function requestSubmit(form: HTMLFormElement, submitter?: HTMLButtonElement | HTMLInputElement): void;
44
+ export declare function requestValidate(form: HTMLFormElement, field?: string): void;
63
45
  export declare function getFormElement(element: HTMLFormElement | HTMLFieldSetElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement | null): HTMLFormElement | null;
64
- export declare type ListCommand<Schema> = {
46
+ export declare function focusFirstInvalidField(form: HTMLFormElement, fields?: string[]): void;
47
+ export declare function getSubmissionType(name: string): string | null;
48
+ export declare function parse<Schema extends Record<string, any>>(payload: FormData | URLSearchParams): Submission<Schema>;
49
+ export declare type Command = {
50
+ name: string;
51
+ value: string;
52
+ };
53
+ export declare type ListCommand<Schema = unknown> = {
65
54
  type: 'prepend';
55
+ scope: string;
66
56
  payload: {
67
57
  defaultValue: Schema;
68
58
  };
69
59
  } | {
70
60
  type: 'append';
61
+ scope: string;
71
62
  payload: {
72
63
  defaultValue: Schema;
73
64
  };
74
65
  } | {
75
66
  type: 'replace';
67
+ scope: string;
76
68
  payload: {
77
69
  defaultValue: Schema;
78
70
  index: number;
79
71
  };
80
72
  } | {
81
73
  type: 'remove';
74
+ scope: string;
82
75
  payload: {
83
76
  index: number;
84
77
  };
85
78
  } | {
86
79
  type: 'reorder';
80
+ scope: string;
87
81
  payload: {
88
82
  from: number;
89
83
  to: number;
90
84
  };
91
85
  };
92
- export declare const listCommandKey = "__conform__";
93
- export declare function serializeListCommand<Schema>(name: string, { type, payload }: ListCommand<Schema>): string;
94
- export declare function parseListCommand<Schema>(serialized: string): [string, ListCommand<Schema>];
95
- export declare function updateList<Type>(list: Array<Type>, command: ListCommand<Type>): Array<Type>;
96
- export declare function applyListCommand(payload: FormData | URLSearchParams): FormData | URLSearchParams;
86
+ export declare function parseListCommand<Schema = unknown>(data: string): ListCommand<Schema>;
87
+ export declare function updateList<Schema>(list: Array<Schema>, command: ListCommand<Schema>): Array<Schema>;
88
+ export declare function handleList<Schema>(submission: Submission<Schema>): Submission<Schema>;
package/index.js CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
6
+
5
7
  function isFieldElement(element) {
6
8
  return element instanceof Element && (element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA' || element.tagName === 'BUTTON');
7
9
  }
8
10
  function getPaths(name) {
9
- var pattern = /(\w+)\[(\d+)\]/;
11
+ var pattern = /(\w*)\[(\d+)\]/;
10
12
 
11
13
  if (!name) {
12
14
  return [];
@@ -19,6 +21,10 @@ function getPaths(name) {
19
21
  return key;
20
22
  }
21
23
 
24
+ if (matches[1] === '') {
25
+ return Number(matches[2]);
26
+ }
27
+
22
28
  return [matches[1], Number(matches[2])];
23
29
  });
24
30
  }
@@ -33,40 +39,37 @@ function getFormData(form, submitter) {
33
39
  }
34
40
  function getName(paths) {
35
41
  return paths.reduce((name, path) => {
36
- if (name === '' || path === '') {
37
- return [name, path].join('');
38
- }
39
-
40
42
  if (typeof path === 'number') {
41
43
  return "".concat(name, "[").concat(path, "]");
42
44
  }
43
45
 
46
+ if (name === '' || path === '') {
47
+ return [name, path].join('');
48
+ }
49
+
44
50
  return [name, path].join('.');
45
51
  }, '');
46
52
  }
47
- function getKey(fieldName) {
48
- var fieldsetName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
49
- var name = fieldsetName === '' || fieldName.startsWith(fieldsetName) ? fieldName.slice(fieldsetName ? fieldsetName.length + 1 : 0) : '';
50
- var paths = getPaths(name);
51
-
52
- if (paths.length > 1) {
53
- return null;
54
- }
55
-
56
- return typeof paths[0] === 'string' ? paths[0] : null;
53
+ function hasError(error, name) {
54
+ return typeof error.find(_ref => {
55
+ var [fieldName, message] = _ref;
56
+ return fieldName === name && message !== '';
57
+ }) !== 'undefined';
57
58
  }
58
- function setFormError(form, errors) {
59
- var firstErrorByName = Object.fromEntries([...errors].reverse());
59
+ function reportValidity(form, state) {
60
+ var firstErrorByName = Object.fromEntries([...state.error].reverse());
60
61
 
61
- for (var _element of form.elements) {
62
- var _firstErrorByName$_el;
62
+ for (var element of form.elements) {
63
+ var _firstErrorByName$ele;
63
64
 
64
- if (!isFieldElement(_element)) {
65
+ if (!isFieldElement(element) || !state.scope.includes(element.name)) {
65
66
  continue;
66
67
  }
67
68
 
68
- _element.setCustomValidity((_firstErrorByName$_el = firstErrorByName[_element.name]) !== null && _firstErrorByName$_el !== void 0 ? _firstErrorByName$_el : '');
69
+ element.setCustomValidity((_firstErrorByName$ele = firstErrorByName[element.name]) !== null && _firstErrorByName$ele !== void 0 ? _firstErrorByName$ele : '');
69
70
  }
71
+
72
+ return form.reportValidity();
70
73
  }
71
74
  function setValue(target, paths, valueFn) {
72
75
  var length = paths.length;
@@ -84,115 +87,145 @@ function setValue(target, paths, valueFn) {
84
87
  pointer = pointer[key];
85
88
  }
86
89
  }
87
- function flatten(data) {
88
- var prefix = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
89
- var entries = [];
90
-
91
- if (typeof data === 'string' || typeof data === 'undefined' || data instanceof File) {
92
- entries.push([prefix, data !== null && data !== void 0 ? data : '']);
93
- } else if (Array.isArray(data)) {
94
- for (var i = 0; i < data.length; i++) {
95
- entries.push(...flatten(data[i], "".concat(prefix, "[").concat(i, "]")));
96
- }
97
- } else {
98
- for (var [key, value] of Object.entries(Object(data))) {
99
- entries.push(...flatten(value, prefix ? "".concat(prefix, ".").concat(key) : key));
100
- }
90
+ function requestSubmit(form, submitter) {
91
+ var submitEvent = new SubmitEvent('submit', {
92
+ bubbles: true,
93
+ cancelable: true,
94
+ submitter
95
+ });
96
+ form.dispatchEvent(submitEvent);
97
+ }
98
+ function requestValidate(form, field) {
99
+ var button = document.createElement('button');
100
+ button.name = 'conform/validate';
101
+ button.value = field !== null && field !== void 0 ? field : '';
102
+ button.hidden = true;
103
+ form.appendChild(button);
104
+ requestSubmit(form, button);
105
+ form.removeChild(button);
106
+ }
107
+ function getFormElement(element) {
108
+ var form = element instanceof HTMLFormElement ? element : element === null || element === void 0 ? void 0 : element.form;
109
+
110
+ if (!form) {
111
+ return null;
101
112
  }
102
113
 
103
- return entries;
114
+ return form;
104
115
  }
105
- function unflatten(entries) {
106
- var result = {};
107
-
108
- var _loop = function _loop(key, value) {
109
- var paths = getPaths(key);
110
- setValue(result, paths, prev => {
111
- if (prev) {
112
- throw new Error('Entry with the same name is not supported');
113
- }
116
+ function focusFirstInvalidField(form, fields) {
117
+ var currentFocus = document.activeElement;
114
118
 
115
- return value;
116
- });
117
- };
119
+ if (!isFieldElement(currentFocus) || currentFocus.tagName !== 'BUTTON' || currentFocus.form !== form) {
120
+ return;
121
+ }
122
+
123
+ for (var field of form.elements) {
124
+ if (isFieldElement(field)) {
125
+ // Focus on the first non button field
126
+ if (!field.validity.valid && field.dataset.conformTouched && field.tagName !== 'BUTTON' && (!fields || fields.includes(field.name))) {
127
+ field.focus();
128
+ break;
129
+ }
130
+ }
131
+ }
132
+ }
133
+ function getSubmissionType(name) {
134
+ var prefix = 'conform/';
118
135
 
119
- for (var [key, value] of entries) {
120
- _loop(key, value);
136
+ if (!name.startsWith(prefix) || name.length <= prefix.length) {
137
+ return null;
121
138
  }
122
139
 
123
- return result;
140
+ return name.slice(prefix.length);
124
141
  }
125
- function createSubmission(payload) {
126
- var value = {};
142
+ function parse(payload) {
143
+ var submission = {
144
+ value: {},
145
+ error: [],
146
+ scope: ['']
147
+ };
127
148
 
128
149
  try {
129
- var modifiedPayload = applyListCommand(payload);
130
- value = unflatten(modifiedPayload.entries());
131
-
132
- if (payload !== modifiedPayload) {
133
- return {
134
- state: 'modified',
135
- form: {
136
- value,
137
- error: {}
150
+ var _loop = function _loop(name, value) {
151
+ var submissionType = getSubmissionType(name);
152
+
153
+ if (submissionType) {
154
+ if (typeof value !== 'string') {
155
+ throw new Error('The conform command could not be used on a file input');
138
156
  }
139
- };
140
- }
141
- } catch (e) {
142
- return {
143
- state: 'rejected',
144
- form: {
145
- value,
146
- error: {
147
- message: e instanceof Error ? e.message : 'Submission failed'
157
+
158
+ if (typeof submission.type !== 'undefined') {
159
+ throw new Error('The conform command could only be set on a button');
148
160
  }
161
+
162
+ submission = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, submission), {}, {
163
+ type: submissionType,
164
+ data: value
165
+ });
166
+ } else {
167
+ var paths = getPaths(name);
168
+ var scopes = paths.reduce((result, path) => {
169
+ if (result.length === 0) {
170
+ if (typeof path !== 'string') {
171
+ throw new Error("Invalid name received: ".concat(name));
172
+ }
173
+
174
+ result.push(path);
175
+ } else {
176
+ var [lastName] = result.slice(-1);
177
+ result.push(getName([lastName, path]));
178
+ }
179
+
180
+ return result;
181
+ }, []);
182
+ submission.scope.push(...scopes);
183
+ setValue(submission.value, paths, prev => {
184
+ if (prev) {
185
+ throw new Error('Entry with the same name is not supported');
186
+ }
187
+
188
+ return value;
189
+ });
149
190
  }
150
191
  };
151
- }
152
192
 
153
- return {
154
- state: 'accepted',
155
- data: value,
156
- form: {
157
- value,
158
- error: {}
193
+ for (var [name, value] of payload.entries()) {
194
+ _loop(name, value);
159
195
  }
160
- };
161
- }
162
- function createValidate(handler) {
163
- return (form, submitter) => {
164
- var formData = getFormData(form, submitter);
165
196
 
166
- for (var _field of form.elements) {
167
- if (isFieldElement(_field)) {
168
- handler(_field, formData);
169
- }
197
+ switch (submission.type) {
198
+ case 'validate':
199
+ if (typeof submission.data !== 'undefined' && submission.data !== '') {
200
+ submission.scope = [submission.data];
201
+ }
202
+
203
+ break;
204
+
205
+ case 'list':
206
+ submission = handleList(submission);
207
+ break;
170
208
  }
171
- };
172
- }
173
- function getFormElement(element) {
174
- var form = element instanceof HTMLFormElement ? element : element === null || element === void 0 ? void 0 : element.form;
209
+ } catch (e) {
210
+ submission.error.push(['', e instanceof Error ? e.message : 'Invalid payload received']);
211
+ } // Remove duplicates
175
212
 
176
- if (!form) {
177
- return null;
178
- }
179
213
 
180
- return form;
214
+ submission.scope = Array.from(new Set(submission.scope));
215
+ return submission;
181
216
  }
182
- var listCommandKey = '__conform__';
183
- function serializeListCommand(name, _ref) {
184
- var {
185
- type,
186
- payload
187
- } = _ref;
188
- return [name, type, JSON.stringify(payload)].join('::');
189
- }
190
- function parseListCommand(serialized) {
191
- var [name, type, json] = serialized.split('::');
192
- return [name, {
193
- type: type,
194
- payload: JSON.parse(json)
195
- }];
217
+ function parseListCommand(data) {
218
+ try {
219
+ var command = JSON.parse(data);
220
+
221
+ if (typeof command.type !== 'string' || !['prepend', 'append', 'replace', 'remove', 'reorder'].includes(command.type)) {
222
+ throw new Error('Unsupported list command type');
223
+ }
224
+
225
+ return command;
226
+ } catch (error) {
227
+ throw new Error("Invalid list command: \"".concat(data, "\"; ").concat(error));
228
+ }
196
229
  }
197
230
  function updateList(list, command) {
198
231
  switch (command.type) {
@@ -223,67 +256,43 @@ function updateList(list, command) {
223
256
  break;
224
257
 
225
258
  default:
226
- throw new Error('Invalid list command');
259
+ throw new Error('Unknown list command received');
227
260
  }
228
261
 
229
262
  return list;
230
263
  }
231
- function applyListCommand(payload) {
232
- var command = payload.get(listCommandKey);
233
-
234
- if (!command) {
235
- return payload;
236
- }
237
-
238
- payload.delete(listCommandKey);
239
-
240
- if (command instanceof File) {
241
- throw new Error("The \"".concat(listCommandKey, "\" key could not be used for file upload"));
264
+ function handleList(submission) {
265
+ if (submission.type !== 'list') {
266
+ return submission;
242
267
  }
243
268
 
244
- var result = new FormData();
245
- var entries = [];
246
- var [key, listCommand] = parseListCommand(command);
247
-
248
- for (var [name, value] of payload) {
249
- if (name.startsWith(key)) {
250
- entries.push([name.replace(key, 'list'), value]);
251
- } else {
252
- result.append(name, value);
269
+ var command = parseListCommand(submission.data);
270
+ var paths = getPaths(command.scope);
271
+ setValue(submission.value, paths, list => {
272
+ if (!Array.isArray(list)) {
273
+ throw new Error('The list command can only be applied to a list');
253
274
  }
254
- }
255
-
256
- var {
257
- list
258
- } = unflatten(entries);
259
-
260
- if (!Array.isArray(list)) {
261
- throw new Error('The list command can only be applied to a list');
262
- }
263
275
 
264
- updateList(list, listCommand);
265
-
266
- for (var [_name, _value] of flatten(list, key)) {
267
- result.append(_name, _value);
268
- }
269
-
270
- return result;
276
+ return updateList(list, command);
277
+ });
278
+ return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, submission), {}, {
279
+ scope: [command.scope]
280
+ });
271
281
  }
272
282
 
273
- exports.applyListCommand = applyListCommand;
274
- exports.createSubmission = createSubmission;
275
- exports.createValidate = createValidate;
276
- exports.flatten = flatten;
283
+ exports.focusFirstInvalidField = focusFirstInvalidField;
277
284
  exports.getFormData = getFormData;
278
285
  exports.getFormElement = getFormElement;
279
- exports.getKey = getKey;
280
286
  exports.getName = getName;
281
287
  exports.getPaths = getPaths;
288
+ exports.getSubmissionType = getSubmissionType;
289
+ exports.handleList = handleList;
290
+ exports.hasError = hasError;
282
291
  exports.isFieldElement = isFieldElement;
283
- exports.listCommandKey = listCommandKey;
292
+ exports.parse = parse;
284
293
  exports.parseListCommand = parseListCommand;
285
- exports.serializeListCommand = serializeListCommand;
286
- exports.setFormError = setFormError;
294
+ exports.reportValidity = reportValidity;
295
+ exports.requestSubmit = requestSubmit;
296
+ exports.requestValidate = requestValidate;
287
297
  exports.setValue = setValue;
288
- exports.unflatten = unflatten;
289
298
  exports.updateList = updateList;
package/module/index.js CHANGED
@@ -1,8 +1,10 @@
1
+ import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
2
+
1
3
  function isFieldElement(element) {
2
4
  return element instanceof Element && (element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA' || element.tagName === 'BUTTON');
3
5
  }
4
6
  function getPaths(name) {
5
- var pattern = /(\w+)\[(\d+)\]/;
7
+ var pattern = /(\w*)\[(\d+)\]/;
6
8
 
7
9
  if (!name) {
8
10
  return [];
@@ -15,6 +17,10 @@ function getPaths(name) {
15
17
  return key;
16
18
  }
17
19
 
20
+ if (matches[1] === '') {
21
+ return Number(matches[2]);
22
+ }
23
+
18
24
  return [matches[1], Number(matches[2])];
19
25
  });
20
26
  }
@@ -29,40 +35,37 @@ function getFormData(form, submitter) {
29
35
  }
30
36
  function getName(paths) {
31
37
  return paths.reduce((name, path) => {
32
- if (name === '' || path === '') {
33
- return [name, path].join('');
34
- }
35
-
36
38
  if (typeof path === 'number') {
37
39
  return "".concat(name, "[").concat(path, "]");
38
40
  }
39
41
 
42
+ if (name === '' || path === '') {
43
+ return [name, path].join('');
44
+ }
45
+
40
46
  return [name, path].join('.');
41
47
  }, '');
42
48
  }
43
- function getKey(fieldName) {
44
- var fieldsetName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
45
- var name = fieldsetName === '' || fieldName.startsWith(fieldsetName) ? fieldName.slice(fieldsetName ? fieldsetName.length + 1 : 0) : '';
46
- var paths = getPaths(name);
47
-
48
- if (paths.length > 1) {
49
- return null;
50
- }
51
-
52
- return typeof paths[0] === 'string' ? paths[0] : null;
49
+ function hasError(error, name) {
50
+ return typeof error.find(_ref => {
51
+ var [fieldName, message] = _ref;
52
+ return fieldName === name && message !== '';
53
+ }) !== 'undefined';
53
54
  }
54
- function setFormError(form, errors) {
55
- var firstErrorByName = Object.fromEntries([...errors].reverse());
55
+ function reportValidity(form, state) {
56
+ var firstErrorByName = Object.fromEntries([...state.error].reverse());
56
57
 
57
- for (var _element of form.elements) {
58
- var _firstErrorByName$_el;
58
+ for (var element of form.elements) {
59
+ var _firstErrorByName$ele;
59
60
 
60
- if (!isFieldElement(_element)) {
61
+ if (!isFieldElement(element) || !state.scope.includes(element.name)) {
61
62
  continue;
62
63
  }
63
64
 
64
- _element.setCustomValidity((_firstErrorByName$_el = firstErrorByName[_element.name]) !== null && _firstErrorByName$_el !== void 0 ? _firstErrorByName$_el : '');
65
+ element.setCustomValidity((_firstErrorByName$ele = firstErrorByName[element.name]) !== null && _firstErrorByName$ele !== void 0 ? _firstErrorByName$ele : '');
65
66
  }
67
+
68
+ return form.reportValidity();
66
69
  }
67
70
  function setValue(target, paths, valueFn) {
68
71
  var length = paths.length;
@@ -80,115 +83,145 @@ function setValue(target, paths, valueFn) {
80
83
  pointer = pointer[key];
81
84
  }
82
85
  }
83
- function flatten(data) {
84
- var prefix = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
85
- var entries = [];
86
-
87
- if (typeof data === 'string' || typeof data === 'undefined' || data instanceof File) {
88
- entries.push([prefix, data !== null && data !== void 0 ? data : '']);
89
- } else if (Array.isArray(data)) {
90
- for (var i = 0; i < data.length; i++) {
91
- entries.push(...flatten(data[i], "".concat(prefix, "[").concat(i, "]")));
92
- }
93
- } else {
94
- for (var [key, value] of Object.entries(Object(data))) {
95
- entries.push(...flatten(value, prefix ? "".concat(prefix, ".").concat(key) : key));
96
- }
86
+ function requestSubmit(form, submitter) {
87
+ var submitEvent = new SubmitEvent('submit', {
88
+ bubbles: true,
89
+ cancelable: true,
90
+ submitter
91
+ });
92
+ form.dispatchEvent(submitEvent);
93
+ }
94
+ function requestValidate(form, field) {
95
+ var button = document.createElement('button');
96
+ button.name = 'conform/validate';
97
+ button.value = field !== null && field !== void 0 ? field : '';
98
+ button.hidden = true;
99
+ form.appendChild(button);
100
+ requestSubmit(form, button);
101
+ form.removeChild(button);
102
+ }
103
+ function getFormElement(element) {
104
+ var form = element instanceof HTMLFormElement ? element : element === null || element === void 0 ? void 0 : element.form;
105
+
106
+ if (!form) {
107
+ return null;
97
108
  }
98
109
 
99
- return entries;
110
+ return form;
100
111
  }
101
- function unflatten(entries) {
102
- var result = {};
103
-
104
- var _loop = function _loop(key, value) {
105
- var paths = getPaths(key);
106
- setValue(result, paths, prev => {
107
- if (prev) {
108
- throw new Error('Entry with the same name is not supported');
109
- }
112
+ function focusFirstInvalidField(form, fields) {
113
+ var currentFocus = document.activeElement;
110
114
 
111
- return value;
112
- });
113
- };
115
+ if (!isFieldElement(currentFocus) || currentFocus.tagName !== 'BUTTON' || currentFocus.form !== form) {
116
+ return;
117
+ }
118
+
119
+ for (var field of form.elements) {
120
+ if (isFieldElement(field)) {
121
+ // Focus on the first non button field
122
+ if (!field.validity.valid && field.dataset.conformTouched && field.tagName !== 'BUTTON' && (!fields || fields.includes(field.name))) {
123
+ field.focus();
124
+ break;
125
+ }
126
+ }
127
+ }
128
+ }
129
+ function getSubmissionType(name) {
130
+ var prefix = 'conform/';
114
131
 
115
- for (var [key, value] of entries) {
116
- _loop(key, value);
132
+ if (!name.startsWith(prefix) || name.length <= prefix.length) {
133
+ return null;
117
134
  }
118
135
 
119
- return result;
136
+ return name.slice(prefix.length);
120
137
  }
121
- function createSubmission(payload) {
122
- var value = {};
138
+ function parse(payload) {
139
+ var submission = {
140
+ value: {},
141
+ error: [],
142
+ scope: ['']
143
+ };
123
144
 
124
145
  try {
125
- var modifiedPayload = applyListCommand(payload);
126
- value = unflatten(modifiedPayload.entries());
127
-
128
- if (payload !== modifiedPayload) {
129
- return {
130
- state: 'modified',
131
- form: {
132
- value,
133
- error: {}
146
+ var _loop = function _loop(name, value) {
147
+ var submissionType = getSubmissionType(name);
148
+
149
+ if (submissionType) {
150
+ if (typeof value !== 'string') {
151
+ throw new Error('The conform command could not be used on a file input');
134
152
  }
135
- };
136
- }
137
- } catch (e) {
138
- return {
139
- state: 'rejected',
140
- form: {
141
- value,
142
- error: {
143
- message: e instanceof Error ? e.message : 'Submission failed'
153
+
154
+ if (typeof submission.type !== 'undefined') {
155
+ throw new Error('The conform command could only be set on a button');
144
156
  }
157
+
158
+ submission = _objectSpread2(_objectSpread2({}, submission), {}, {
159
+ type: submissionType,
160
+ data: value
161
+ });
162
+ } else {
163
+ var paths = getPaths(name);
164
+ var scopes = paths.reduce((result, path) => {
165
+ if (result.length === 0) {
166
+ if (typeof path !== 'string') {
167
+ throw new Error("Invalid name received: ".concat(name));
168
+ }
169
+
170
+ result.push(path);
171
+ } else {
172
+ var [lastName] = result.slice(-1);
173
+ result.push(getName([lastName, path]));
174
+ }
175
+
176
+ return result;
177
+ }, []);
178
+ submission.scope.push(...scopes);
179
+ setValue(submission.value, paths, prev => {
180
+ if (prev) {
181
+ throw new Error('Entry with the same name is not supported');
182
+ }
183
+
184
+ return value;
185
+ });
145
186
  }
146
187
  };
147
- }
148
188
 
149
- return {
150
- state: 'accepted',
151
- data: value,
152
- form: {
153
- value,
154
- error: {}
189
+ for (var [name, value] of payload.entries()) {
190
+ _loop(name, value);
155
191
  }
156
- };
157
- }
158
- function createValidate(handler) {
159
- return (form, submitter) => {
160
- var formData = getFormData(form, submitter);
161
192
 
162
- for (var _field of form.elements) {
163
- if (isFieldElement(_field)) {
164
- handler(_field, formData);
165
- }
193
+ switch (submission.type) {
194
+ case 'validate':
195
+ if (typeof submission.data !== 'undefined' && submission.data !== '') {
196
+ submission.scope = [submission.data];
197
+ }
198
+
199
+ break;
200
+
201
+ case 'list':
202
+ submission = handleList(submission);
203
+ break;
166
204
  }
167
- };
168
- }
169
- function getFormElement(element) {
170
- var form = element instanceof HTMLFormElement ? element : element === null || element === void 0 ? void 0 : element.form;
205
+ } catch (e) {
206
+ submission.error.push(['', e instanceof Error ? e.message : 'Invalid payload received']);
207
+ } // Remove duplicates
171
208
 
172
- if (!form) {
173
- return null;
174
- }
175
209
 
176
- return form;
210
+ submission.scope = Array.from(new Set(submission.scope));
211
+ return submission;
177
212
  }
178
- var listCommandKey = '__conform__';
179
- function serializeListCommand(name, _ref) {
180
- var {
181
- type,
182
- payload
183
- } = _ref;
184
- return [name, type, JSON.stringify(payload)].join('::');
185
- }
186
- function parseListCommand(serialized) {
187
- var [name, type, json] = serialized.split('::');
188
- return [name, {
189
- type: type,
190
- payload: JSON.parse(json)
191
- }];
213
+ function parseListCommand(data) {
214
+ try {
215
+ var command = JSON.parse(data);
216
+
217
+ if (typeof command.type !== 'string' || !['prepend', 'append', 'replace', 'remove', 'reorder'].includes(command.type)) {
218
+ throw new Error('Unsupported list command type');
219
+ }
220
+
221
+ return command;
222
+ } catch (error) {
223
+ throw new Error("Invalid list command: \"".concat(data, "\"; ").concat(error));
224
+ }
192
225
  }
193
226
  function updateList(list, command) {
194
227
  switch (command.type) {
@@ -219,51 +252,28 @@ function updateList(list, command) {
219
252
  break;
220
253
 
221
254
  default:
222
- throw new Error('Invalid list command');
255
+ throw new Error('Unknown list command received');
223
256
  }
224
257
 
225
258
  return list;
226
259
  }
227
- function applyListCommand(payload) {
228
- var command = payload.get(listCommandKey);
229
-
230
- if (!command) {
231
- return payload;
232
- }
233
-
234
- payload.delete(listCommandKey);
235
-
236
- if (command instanceof File) {
237
- throw new Error("The \"".concat(listCommandKey, "\" key could not be used for file upload"));
260
+ function handleList(submission) {
261
+ if (submission.type !== 'list') {
262
+ return submission;
238
263
  }
239
264
 
240
- var result = new FormData();
241
- var entries = [];
242
- var [key, listCommand] = parseListCommand(command);
243
-
244
- for (var [name, value] of payload) {
245
- if (name.startsWith(key)) {
246
- entries.push([name.replace(key, 'list'), value]);
247
- } else {
248
- result.append(name, value);
265
+ var command = parseListCommand(submission.data);
266
+ var paths = getPaths(command.scope);
267
+ setValue(submission.value, paths, list => {
268
+ if (!Array.isArray(list)) {
269
+ throw new Error('The list command can only be applied to a list');
249
270
  }
250
- }
251
-
252
- var {
253
- list
254
- } = unflatten(entries);
255
-
256
- if (!Array.isArray(list)) {
257
- throw new Error('The list command can only be applied to a list');
258
- }
259
271
 
260
- updateList(list, listCommand);
261
-
262
- for (var [_name, _value] of flatten(list, key)) {
263
- result.append(_name, _value);
264
- }
265
-
266
- return result;
272
+ return updateList(list, command);
273
+ });
274
+ return _objectSpread2(_objectSpread2({}, submission), {}, {
275
+ scope: [command.scope]
276
+ });
267
277
  }
268
278
 
269
- export { applyListCommand, createSubmission, createValidate, flatten, getFormData, getFormElement, getKey, getName, getPaths, isFieldElement, listCommandKey, parseListCommand, serializeListCommand, setFormError, setValue, unflatten, updateList };
279
+ export { focusFirstInvalidField, getFormData, getFormElement, getName, getPaths, getSubmissionType, handleList, hasError, isFieldElement, parse, parseListCommand, reportValidity, requestSubmit, requestValidate, setValue, updateList };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@conform-to/dom",
3
3
  "description": "A set of opinionated helpers built on top of the Constraint Validation API",
4
4
  "license": "MIT",
5
- "version": "0.3.0",
5
+ "version": "0.4.0-pre.0",
6
6
  "main": "index.js",
7
7
  "module": "module/index.js",
8
8
  "repository": {