@conform-to/dom 0.1.0 → 0.3.0-pre.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,24 +4,22 @@
4
4
 
5
5
  ## API Reference
6
6
 
7
- - [createControlButton](#createControlButton)
8
- - [createFieldConfig](#createFieldConfig)
9
- - [getFieldElements](#getFieldElements)
7
+ - [getFieldProps](#getFieldProps)
8
+ - [getFieldsetData](#getFieldsetData)
10
9
  - [getName](#getName)
11
10
  - [getPaths](#getPaths)
12
11
  - [isFieldElement](#isFieldElement)
12
+ - [isFieldsetField](#isFieldsetField)
13
13
  - [parse](#parse)
14
14
  - [reportValidity](#reportValidity)
15
15
  - [setFieldState](#setFieldState)
16
+ - [setFieldsetError](#setFieldsetError)
16
17
  - [shouldSkipValidate](#shouldSkipValidate)
17
- - [setFieldState](#setFieldState)
18
18
  - [transform](#transform)
19
19
 
20
- ### createControlButton
21
-
22
- ### createFieldConfig
20
+ ### getFieldProps
23
21
 
24
- ### getFieldElements
22
+ ### getFieldsetData
25
23
 
26
24
  ### getName
27
25
 
@@ -29,7 +27,7 @@
29
27
 
30
28
  ### isFieldElement
31
29
 
32
- ### isFieldsetElement
30
+ ### isFieldsetField
33
31
 
34
32
  ### parse
35
33
 
@@ -37,6 +35,8 @@
37
35
 
38
36
  ### setFieldState
39
37
 
38
+ ### setFieldsetError
39
+
40
40
  ### shouldSkipValidate
41
41
 
42
42
  ### transform
@@ -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/index.d.ts CHANGED
@@ -1,84 +1,96 @@
1
- export declare type Constraint<Type> = (Type extends string | number | Date | undefined ? {
1
+ export declare type Primitive = null | undefined | string | number | boolean | Date;
2
+ export declare type FieldElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement;
3
+ export interface FieldConfig<Schema = unknown> extends FieldConstraint {
4
+ name: string;
5
+ defaultValue?: FieldValue<Schema>;
6
+ initialError?: FieldError<Schema>['details'];
7
+ form?: string;
8
+ }
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
+ [Key in keyof Schema]?: FieldValue<Schema[Key]>;
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
+ export declare type FieldConstraint = {
2
19
  required?: boolean;
3
20
  minLength?: number;
4
21
  maxLength?: number;
5
- min?: string;
6
- max?: string;
22
+ min?: string | number;
23
+ max?: string | number;
7
24
  step?: string;
8
25
  multiple?: boolean;
9
26
  pattern?: string;
10
- } : {}) & (undefined extends Type ? {
11
- required?: false;
12
- } : {
13
- required: true;
14
- }) & (Type extends Array<any> ? {
15
- multiple: true;
16
- } : {
17
- multiple?: false;
18
- });
19
- export interface FieldConfig<Type = any> {
20
- name: string;
21
- initialValue?: FieldsetData<Type, string>;
22
- error?: FieldsetData<Type, string>;
23
- form?: string;
24
- constraint?: Constraint<Type>;
25
- }
26
- export declare type Schema<Type extends Record<string, any>> = {
27
- fields: {
28
- [Key in keyof Type]-?: Constraint<Type[Key]>;
29
- };
30
- validate?: (element: FieldsetElement) => void;
31
27
  };
32
- /**
33
- * Data structure of the form value
34
- */
35
- export declare type FieldsetData<Type, Value> = Type extends string | number | Date | undefined ? Value : Type extends Array<infer InnerType> ? Array<FieldsetData<InnerType, Value>> : Type extends Object ? {
36
- [Key in keyof Type]?: FieldsetData<Type[Key], Value>;
37
- } : unknown;
38
- /**
39
- * Element that maintains a list of fields
40
- * i.e. fieldset.elements
41
- */
42
- export declare type FieldsetElement = HTMLFormElement | HTMLFieldSetElement;
43
- /**
44
- * Element type that might be a candiate of Constraint Validation
45
- */
46
- export declare type FieldElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement;
47
- export declare type FormResult<T> = {
48
- state: 'processed';
49
- value: FieldsetData<T, string> | null;
50
- error: FieldsetData<T, string> | null;
28
+ export declare type FieldsetConstraint<Schema extends Record<string, any>> = {
29
+ [Key in keyof Schema]?: FieldConstraint;
30
+ };
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>> {
38
+ 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>;
51
44
  } | {
52
45
  state: 'rejected';
53
- value: FieldsetData<T, string> | null;
54
- error: FieldsetData<T, string>;
46
+ form: FormState<T>;
55
47
  } | {
56
48
  state: 'accepted';
57
- value: T;
49
+ data: T;
50
+ form: FormState<T>;
58
51
  };
59
- export declare function isFieldsetElement(element: unknown): element is FieldsetElement;
60
52
  export declare function isFieldElement(element: unknown): element is FieldElement;
61
- export declare function setFieldState(field: unknown, state: {
62
- touched: boolean;
63
- }): void;
64
- export declare function reportValidity(fieldset: FieldsetElement): boolean;
65
- export declare function createFieldConfig<Type extends Record<string, any>>(schema: Schema<Type>, options: {
66
- name?: string;
67
- form?: string;
68
- initialValue?: FieldsetData<Type, string>;
69
- error?: FieldsetData<Type, string>;
70
- }): {
71
- [Key in keyof Type]-?: FieldConfig<Type[Key]>;
72
- };
73
- export declare function shouldSkipValidate(event: SubmitEvent): boolean;
74
53
  export declare function getPaths(name?: string): Array<string | number>;
54
+ export declare function getFormData(form: HTMLFormElement, submitter?: HTMLInputElement | HTMLButtonElement | null): FormData;
75
55
  export declare function getName(paths: Array<string | number>): string;
76
- export declare function transform(entries: Array<[string, FormDataEntryValue]> | Iterable<[string, FormDataEntryValue]>): unknown;
77
- export declare function createControlButton(name: string, action: 'prepend' | 'append' | 'remove', data: any): {
78
- type: 'submit';
79
- name: string;
80
- value: string;
81
- formNoValidate: boolean;
56
+ export declare function getKey(fieldName: string, fieldsetName?: string): string | null;
57
+ export declare function setFormError(form: HTMLFormElement, errors: Array<[string, string]>): void;
58
+ 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;
63
+ export declare function getFormElement(element: HTMLFormElement | HTMLFieldSetElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement | null): HTMLFormElement | null;
64
+ export declare type ListCommand<Schema> = {
65
+ type: 'prepend';
66
+ payload: {
67
+ defaultValue: Schema;
68
+ };
69
+ } | {
70
+ type: 'append';
71
+ payload: {
72
+ defaultValue: Schema;
73
+ };
74
+ } | {
75
+ type: 'replace';
76
+ payload: {
77
+ defaultValue: Schema;
78
+ index: number;
79
+ };
80
+ } | {
81
+ type: 'remove';
82
+ payload: {
83
+ index: number;
84
+ };
85
+ } | {
86
+ type: 'reorder';
87
+ payload: {
88
+ from: number;
89
+ to: number;
90
+ };
82
91
  };
83
- export declare function parse(payload: FormData | URLSearchParams): FormResult<unknown>;
84
- export declare function getFieldElements(fieldset: FieldsetElement, key: string): FieldElement[];
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;
package/index.js CHANGED
@@ -2,84 +2,9 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- /**
6
- * Data structure of the form value
7
- */
8
-
9
- /**
10
- * Element that maintains a list of fields
11
- * i.e. fieldset.elements
12
- */
13
-
14
- /**
15
- * Element type that might be a candiate of Constraint Validation
16
- */
17
- function isFieldsetElement(element) {
18
- return element instanceof Element && (element.tagName === 'FORM' || element.tagName === 'FIELDSET');
19
- }
20
5
  function isFieldElement(element) {
21
6
  return element instanceof Element && (element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA' || element.tagName === 'BUTTON');
22
7
  }
23
- function setFieldState(field, state) {
24
- if (isFieldsetElement(field)) {
25
- for (var _element of field.elements) {
26
- setFieldState(_element, state);
27
- }
28
-
29
- return;
30
- }
31
-
32
- if (!isFieldElement(field)) {
33
- console.warn('Only input/select/textarea/button element can be touched');
34
- return;
35
- }
36
-
37
- if (state.touched) {
38
- field.dataset.touched = 'true';
39
- } else {
40
- delete field.dataset.touched;
41
- }
42
- }
43
- function reportValidity(fieldset) {
44
- var isValid = true;
45
-
46
- for (var field of fieldset.elements) {
47
- if (isFieldElement(field) && field.dataset.touched && !field.checkValidity()) {
48
- isValid = false;
49
- }
50
- }
51
-
52
- return isValid;
53
- }
54
- function createFieldConfig(schema, options) {
55
- var result = {};
56
-
57
- for (var key of Object.keys(schema.fields)) {
58
- var _options$initialValue, _options$error;
59
-
60
- var constraint = schema.fields[key];
61
- var config = {
62
- name: options.name ? "".concat(options.name, ".").concat(key) : key,
63
- form: options.form,
64
- initialValue: (_options$initialValue = options.initialValue) === null || _options$initialValue === void 0 ? void 0 : _options$initialValue[key],
65
- error: (_options$error = options.error) === null || _options$error === void 0 ? void 0 : _options$error[key],
66
- // @ts-expect-error
67
- constraint
68
- };
69
- result[key] = config;
70
- }
71
-
72
- return result;
73
- }
74
- function shouldSkipValidate(event) {
75
- var _event$submitter, _event$submitter2;
76
-
77
- if (((_event$submitter = event.submitter) === null || _event$submitter === void 0 ? void 0 : _event$submitter.tagName) === 'BUTTON' || ((_event$submitter2 = event.submitter) === null || _event$submitter2 === void 0 ? void 0 : _event$submitter2.tagName) === 'INPUT') {
78
- return event.submitter.formNoValidate;
79
- }
80
-
81
- return false;
82
- }
83
8
  function getPaths(name) {
84
9
  var pattern = /(\w+)\[(\d+)\]/;
85
10
 
@@ -97,6 +22,15 @@ function getPaths(name) {
97
22
  return [matches[1], Number(matches[2])];
98
23
  });
99
24
  }
25
+ function getFormData(form, submitter) {
26
+ var payload = new FormData(form);
27
+
28
+ if (submitter !== null && submitter !== void 0 && submitter.name) {
29
+ payload.append(submitter.name, submitter.value);
30
+ }
31
+
32
+ return payload;
33
+ }
100
34
  function getName(paths) {
101
35
  return paths.reduce((name, path) => {
102
36
  if (name === '' || path === '') {
@@ -110,144 +44,246 @@ function getName(paths) {
110
44
  return [name, path].join('.');
111
45
  }, '');
112
46
  }
113
- function transform(entries) {
114
- var result = {};
115
-
116
- for (var [key, value] of entries) {
117
- var paths = getPaths(key);
118
- var length = paths.length;
119
- var lastIndex = length - 1;
120
- var index = -1;
121
- var pointer = result;
122
-
123
- while (pointer != null && ++index < length) {
124
- var _key = paths[index];
125
- var next = paths[index + 1];
126
- var newValue = value;
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);
127
51
 
128
- if (index != lastIndex) {
129
- var _pointer$_key;
130
-
131
- newValue = (_pointer$_key = pointer[_key]) !== null && _pointer$_key !== void 0 ? _pointer$_key : typeof next === 'number' ? [] : {};
132
- } // if (typeof pointer[key] !== 'undefined') {
133
- // pointer[key] = Array.isArray(pointer[key])
134
- // ? pointer[key].concat(newValue)
135
- // : [pointer[key], newValue];
136
- // } else {
52
+ if (paths.length > 1) {
53
+ return null;
54
+ }
137
55
 
56
+ return typeof paths[0] === 'string' ? paths[0] : null;
57
+ }
58
+ function setFormError(form, errors) {
59
+ var firstErrorByName = Object.fromEntries([...errors].reverse());
138
60
 
139
- pointer[_key] = newValue; // }
61
+ for (var _element of form.elements) {
62
+ var _firstErrorByName$_el;
140
63
 
141
- pointer = pointer[_key];
64
+ if (!isFieldElement(_element)) {
65
+ continue;
142
66
  }
143
- }
144
67
 
145
- return result;
68
+ _element.setCustomValidity((_firstErrorByName$_el = firstErrorByName[_element.name]) !== null && _firstErrorByName$_el !== void 0 ? _firstErrorByName$_el : '');
69
+ }
146
70
  }
147
- function createControlButton(name, action, data) {
148
- return {
149
- type: 'submit',
150
- name: '__conform__',
151
- value: [name, action, JSON.stringify(data)].join('::'),
152
- formNoValidate: true
153
- };
71
+ function setValue(target, paths, valueFn) {
72
+ var length = paths.length;
73
+ var lastIndex = length - 1;
74
+ var index = -1;
75
+ var pointer = target;
76
+
77
+ while (pointer != null && ++index < length) {
78
+ var _pointer$key;
79
+
80
+ var key = paths[index];
81
+ var next = paths[index + 1];
82
+ var newValue = index != lastIndex ? (_pointer$key = pointer[key]) !== null && _pointer$key !== void 0 ? _pointer$key : typeof next === 'number' ? [] : {} : valueFn(pointer[key]);
83
+ pointer[key] = newValue;
84
+ pointer = pointer[key];
85
+ }
154
86
  }
155
- function parse(payload) {
156
- var command = payload.get('__conform__');
157
-
158
- if (command) {
159
- payload.delete('__conform__');
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
+ }
160
101
  }
161
102
 
162
- var value = transform(payload.entries());
103
+ return entries;
104
+ }
105
+ function unflatten(entries) {
106
+ var result = {};
163
107
 
164
- if (command) {
165
- try {
166
- if (command instanceof File) {
167
- throw new Error('The __conform__ key is reserved for special command and could not be used for file upload.');
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');
168
113
  }
169
114
 
170
- var [name, action, json] = command.split('::');
171
- var list = value;
115
+ return value;
116
+ });
117
+ };
172
118
 
173
- for (var path of getPaths(name)) {
174
- list = list[path];
119
+ for (var [key, value] of entries) {
120
+ _loop(key, value);
121
+ }
175
122
 
176
- if (typeof list === 'undefined') {
177
- break;
178
- }
179
- }
123
+ return result;
124
+ }
125
+ function createSubmission(payload) {
126
+ var value = {};
180
127
 
181
- if (!Array.isArray(list)) {
182
- throw new Error('');
183
- }
128
+ try {
129
+ var modifiedPayload = applyListCommand(payload);
130
+ value = unflatten(modifiedPayload.entries());
184
131
 
185
- switch (action) {
186
- case 'prepend':
187
- {
188
- var initialValue = JSON.parse(json);
189
- list.unshift(initialValue);
190
- break;
191
- }
192
-
193
- case 'append':
194
- {
195
- var _initialValue = JSON.parse(json);
196
-
197
- list.push(_initialValue);
198
- break;
199
- }
200
-
201
- case 'remove':
202
- var {
203
- index
204
- } = JSON.parse(json);
205
- list.splice(index, 1);
206
- break;
207
-
208
- default:
209
- throw new Error('Invalid action found; Only `prepend`, `append` and `remove` is accepted');
210
- }
211
- } catch (e) {
132
+ if (payload !== modifiedPayload) {
212
133
  return {
213
- state: 'rejected',
214
- value,
215
- error: {
216
- __conform__: e instanceof Error ? e.message : 'Something went wrong'
134
+ state: 'modified',
135
+ form: {
136
+ value,
137
+ error: {}
217
138
  }
218
139
  };
219
140
  }
220
-
141
+ } catch (e) {
221
142
  return {
222
- state: 'processed',
223
- value,
224
- error: null
143
+ state: 'rejected',
144
+ form: {
145
+ value,
146
+ error: {
147
+ message: e instanceof Error ? e.message : 'Submission failed'
148
+ }
149
+ }
225
150
  };
226
151
  }
227
152
 
228
153
  return {
229
154
  state: 'accepted',
230
- value
155
+ data: value,
156
+ form: {
157
+ value,
158
+ error: {}
159
+ }
160
+ };
161
+ }
162
+ function createValidate(handler) {
163
+ return (form, submitter) => {
164
+ var formData = getFormData(form, submitter);
165
+
166
+ for (var _field of form.elements) {
167
+ if (isFieldElement(_field)) {
168
+ handler(_field, formData);
169
+ }
170
+ }
231
171
  };
232
172
  }
233
- function getFieldElements(fieldset, key) {
234
- var _fieldset$name;
173
+ function getFormElement(element) {
174
+ var form = element instanceof HTMLFormElement ? element : element === null || element === void 0 ? void 0 : element.form;
175
+
176
+ if (!form) {
177
+ return null;
178
+ }
235
179
 
236
- var name = getName([(_fieldset$name = fieldset.name) !== null && _fieldset$name !== void 0 ? _fieldset$name : '', key]);
237
- var item = fieldset.elements.namedItem(name);
238
- var nodes = item instanceof RadioNodeList ? Array.from(item) : item !== null ? [item] : [];
239
- return nodes.filter(isFieldElement);
180
+ return form;
181
+ }
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
+ }];
196
+ }
197
+ function updateList(list, command) {
198
+ switch (command.type) {
199
+ case 'prepend':
200
+ {
201
+ list.unshift(command.payload.defaultValue);
202
+ break;
203
+ }
204
+
205
+ case 'append':
206
+ {
207
+ list.push(command.payload.defaultValue);
208
+ break;
209
+ }
210
+
211
+ case 'replace':
212
+ {
213
+ list.splice(command.payload.index, 1, command.payload.defaultValue);
214
+ break;
215
+ }
216
+
217
+ case 'remove':
218
+ list.splice(command.payload.index, 1);
219
+ break;
220
+
221
+ case 'reorder':
222
+ list.splice(command.payload.to, 0, ...list.splice(command.payload.from, 1));
223
+ break;
224
+
225
+ default:
226
+ throw new Error('Invalid list command');
227
+ }
228
+
229
+ return list;
230
+ }
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"));
242
+ }
243
+
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);
253
+ }
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
+
264
+ updateList(list, listCommand);
265
+
266
+ for (var [_name, _value] of flatten(list, key)) {
267
+ result.append(_name, _value);
268
+ }
269
+
270
+ return result;
240
271
  }
241
272
 
242
- exports.createControlButton = createControlButton;
243
- exports.createFieldConfig = createFieldConfig;
244
- exports.getFieldElements = getFieldElements;
273
+ exports.applyListCommand = applyListCommand;
274
+ exports.createSubmission = createSubmission;
275
+ exports.createValidate = createValidate;
276
+ exports.flatten = flatten;
277
+ exports.getFormData = getFormData;
278
+ exports.getFormElement = getFormElement;
279
+ exports.getKey = getKey;
245
280
  exports.getName = getName;
246
281
  exports.getPaths = getPaths;
247
282
  exports.isFieldElement = isFieldElement;
248
- exports.isFieldsetElement = isFieldsetElement;
249
- exports.parse = parse;
250
- exports.reportValidity = reportValidity;
251
- exports.setFieldState = setFieldState;
252
- exports.shouldSkipValidate = shouldSkipValidate;
253
- exports.transform = transform;
283
+ exports.listCommandKey = listCommandKey;
284
+ exports.parseListCommand = parseListCommand;
285
+ exports.serializeListCommand = serializeListCommand;
286
+ exports.setFormError = setFormError;
287
+ exports.setValue = setValue;
288
+ exports.unflatten = unflatten;
289
+ exports.updateList = updateList;
@@ -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 };
package/module/index.js CHANGED
@@ -1,81 +1,6 @@
1
- /**
2
- * Data structure of the form value
3
- */
4
-
5
- /**
6
- * Element that maintains a list of fields
7
- * i.e. fieldset.elements
8
- */
9
-
10
- /**
11
- * Element type that might be a candiate of Constraint Validation
12
- */
13
- function isFieldsetElement(element) {
14
- return element instanceof Element && (element.tagName === 'FORM' || element.tagName === 'FIELDSET');
15
- }
16
1
  function isFieldElement(element) {
17
2
  return element instanceof Element && (element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA' || element.tagName === 'BUTTON');
18
3
  }
19
- function setFieldState(field, state) {
20
- if (isFieldsetElement(field)) {
21
- for (var _element of field.elements) {
22
- setFieldState(_element, state);
23
- }
24
-
25
- return;
26
- }
27
-
28
- if (!isFieldElement(field)) {
29
- console.warn('Only input/select/textarea/button element can be touched');
30
- return;
31
- }
32
-
33
- if (state.touched) {
34
- field.dataset.touched = 'true';
35
- } else {
36
- delete field.dataset.touched;
37
- }
38
- }
39
- function reportValidity(fieldset) {
40
- var isValid = true;
41
-
42
- for (var field of fieldset.elements) {
43
- if (isFieldElement(field) && field.dataset.touched && !field.checkValidity()) {
44
- isValid = false;
45
- }
46
- }
47
-
48
- return isValid;
49
- }
50
- function createFieldConfig(schema, options) {
51
- var result = {};
52
-
53
- for (var key of Object.keys(schema.fields)) {
54
- var _options$initialValue, _options$error;
55
-
56
- var constraint = schema.fields[key];
57
- var config = {
58
- name: options.name ? "".concat(options.name, ".").concat(key) : key,
59
- form: options.form,
60
- initialValue: (_options$initialValue = options.initialValue) === null || _options$initialValue === void 0 ? void 0 : _options$initialValue[key],
61
- error: (_options$error = options.error) === null || _options$error === void 0 ? void 0 : _options$error[key],
62
- // @ts-expect-error
63
- constraint
64
- };
65
- result[key] = config;
66
- }
67
-
68
- return result;
69
- }
70
- function shouldSkipValidate(event) {
71
- var _event$submitter, _event$submitter2;
72
-
73
- if (((_event$submitter = event.submitter) === null || _event$submitter === void 0 ? void 0 : _event$submitter.tagName) === 'BUTTON' || ((_event$submitter2 = event.submitter) === null || _event$submitter2 === void 0 ? void 0 : _event$submitter2.tagName) === 'INPUT') {
74
- return event.submitter.formNoValidate;
75
- }
76
-
77
- return false;
78
- }
79
4
  function getPaths(name) {
80
5
  var pattern = /(\w+)\[(\d+)\]/;
81
6
 
@@ -93,6 +18,15 @@ function getPaths(name) {
93
18
  return [matches[1], Number(matches[2])];
94
19
  });
95
20
  }
21
+ function getFormData(form, submitter) {
22
+ var payload = new FormData(form);
23
+
24
+ if (submitter !== null && submitter !== void 0 && submitter.name) {
25
+ payload.append(submitter.name, submitter.value);
26
+ }
27
+
28
+ return payload;
29
+ }
96
30
  function getName(paths) {
97
31
  return paths.reduce((name, path) => {
98
32
  if (name === '' || path === '') {
@@ -106,133 +40,230 @@ function getName(paths) {
106
40
  return [name, path].join('.');
107
41
  }, '');
108
42
  }
109
- function transform(entries) {
110
- var result = {};
111
-
112
- for (var [key, value] of entries) {
113
- var paths = getPaths(key);
114
- var length = paths.length;
115
- var lastIndex = length - 1;
116
- var index = -1;
117
- var pointer = result;
118
-
119
- while (pointer != null && ++index < length) {
120
- var _key = paths[index];
121
- var next = paths[index + 1];
122
- var newValue = value;
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);
123
47
 
124
- if (index != lastIndex) {
125
- var _pointer$_key;
126
-
127
- newValue = (_pointer$_key = pointer[_key]) !== null && _pointer$_key !== void 0 ? _pointer$_key : typeof next === 'number' ? [] : {};
128
- } // if (typeof pointer[key] !== 'undefined') {
129
- // pointer[key] = Array.isArray(pointer[key])
130
- // ? pointer[key].concat(newValue)
131
- // : [pointer[key], newValue];
132
- // } else {
48
+ if (paths.length > 1) {
49
+ return null;
50
+ }
133
51
 
52
+ return typeof paths[0] === 'string' ? paths[0] : null;
53
+ }
54
+ function setFormError(form, errors) {
55
+ var firstErrorByName = Object.fromEntries([...errors].reverse());
134
56
 
135
- pointer[_key] = newValue; // }
57
+ for (var _element of form.elements) {
58
+ var _firstErrorByName$_el;
136
59
 
137
- pointer = pointer[_key];
60
+ if (!isFieldElement(_element)) {
61
+ continue;
138
62
  }
139
- }
140
63
 
141
- return result;
64
+ _element.setCustomValidity((_firstErrorByName$_el = firstErrorByName[_element.name]) !== null && _firstErrorByName$_el !== void 0 ? _firstErrorByName$_el : '');
65
+ }
142
66
  }
143
- function createControlButton(name, action, data) {
144
- return {
145
- type: 'submit',
146
- name: '__conform__',
147
- value: [name, action, JSON.stringify(data)].join('::'),
148
- formNoValidate: true
149
- };
67
+ function setValue(target, paths, valueFn) {
68
+ var length = paths.length;
69
+ var lastIndex = length - 1;
70
+ var index = -1;
71
+ var pointer = target;
72
+
73
+ while (pointer != null && ++index < length) {
74
+ var _pointer$key;
75
+
76
+ var key = paths[index];
77
+ var next = paths[index + 1];
78
+ var newValue = index != lastIndex ? (_pointer$key = pointer[key]) !== null && _pointer$key !== void 0 ? _pointer$key : typeof next === 'number' ? [] : {} : valueFn(pointer[key]);
79
+ pointer[key] = newValue;
80
+ pointer = pointer[key];
81
+ }
150
82
  }
151
- function parse(payload) {
152
- var command = payload.get('__conform__');
153
-
154
- if (command) {
155
- payload.delete('__conform__');
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
+ }
156
97
  }
157
98
 
158
- var value = transform(payload.entries());
99
+ return entries;
100
+ }
101
+ function unflatten(entries) {
102
+ var result = {};
159
103
 
160
- if (command) {
161
- try {
162
- if (command instanceof File) {
163
- throw new Error('The __conform__ key is reserved for special command and could not be used for file upload.');
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');
164
109
  }
165
110
 
166
- var [name, action, json] = command.split('::');
167
- var list = value;
111
+ return value;
112
+ });
113
+ };
168
114
 
169
- for (var path of getPaths(name)) {
170
- list = list[path];
115
+ for (var [key, value] of entries) {
116
+ _loop(key, value);
117
+ }
171
118
 
172
- if (typeof list === 'undefined') {
173
- break;
174
- }
175
- }
119
+ return result;
120
+ }
121
+ function createSubmission(payload) {
122
+ var value = {};
176
123
 
177
- if (!Array.isArray(list)) {
178
- throw new Error('');
179
- }
124
+ try {
125
+ var modifiedPayload = applyListCommand(payload);
126
+ value = unflatten(modifiedPayload.entries());
180
127
 
181
- switch (action) {
182
- case 'prepend':
183
- {
184
- var initialValue = JSON.parse(json);
185
- list.unshift(initialValue);
186
- break;
187
- }
188
-
189
- case 'append':
190
- {
191
- var _initialValue = JSON.parse(json);
192
-
193
- list.push(_initialValue);
194
- break;
195
- }
196
-
197
- case 'remove':
198
- var {
199
- index
200
- } = JSON.parse(json);
201
- list.splice(index, 1);
202
- break;
203
-
204
- default:
205
- throw new Error('Invalid action found; Only `prepend`, `append` and `remove` is accepted');
206
- }
207
- } catch (e) {
128
+ if (payload !== modifiedPayload) {
208
129
  return {
209
- state: 'rejected',
210
- value,
211
- error: {
212
- __conform__: e instanceof Error ? e.message : 'Something went wrong'
130
+ state: 'modified',
131
+ form: {
132
+ value,
133
+ error: {}
213
134
  }
214
135
  };
215
136
  }
216
-
137
+ } catch (e) {
217
138
  return {
218
- state: 'processed',
219
- value,
220
- error: null
139
+ state: 'rejected',
140
+ form: {
141
+ value,
142
+ error: {
143
+ message: e instanceof Error ? e.message : 'Submission failed'
144
+ }
145
+ }
221
146
  };
222
147
  }
223
148
 
224
149
  return {
225
150
  state: 'accepted',
226
- value
151
+ data: value,
152
+ form: {
153
+ value,
154
+ error: {}
155
+ }
156
+ };
157
+ }
158
+ function createValidate(handler) {
159
+ return (form, submitter) => {
160
+ var formData = getFormData(form, submitter);
161
+
162
+ for (var _field of form.elements) {
163
+ if (isFieldElement(_field)) {
164
+ handler(_field, formData);
165
+ }
166
+ }
227
167
  };
228
168
  }
229
- function getFieldElements(fieldset, key) {
230
- var _fieldset$name;
169
+ function getFormElement(element) {
170
+ var form = element instanceof HTMLFormElement ? element : element === null || element === void 0 ? void 0 : element.form;
171
+
172
+ if (!form) {
173
+ return null;
174
+ }
231
175
 
232
- var name = getName([(_fieldset$name = fieldset.name) !== null && _fieldset$name !== void 0 ? _fieldset$name : '', key]);
233
- var item = fieldset.elements.namedItem(name);
234
- var nodes = item instanceof RadioNodeList ? Array.from(item) : item !== null ? [item] : [];
235
- return nodes.filter(isFieldElement);
176
+ return form;
177
+ }
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
+ }];
192
+ }
193
+ function updateList(list, command) {
194
+ switch (command.type) {
195
+ case 'prepend':
196
+ {
197
+ list.unshift(command.payload.defaultValue);
198
+ break;
199
+ }
200
+
201
+ case 'append':
202
+ {
203
+ list.push(command.payload.defaultValue);
204
+ break;
205
+ }
206
+
207
+ case 'replace':
208
+ {
209
+ list.splice(command.payload.index, 1, command.payload.defaultValue);
210
+ break;
211
+ }
212
+
213
+ case 'remove':
214
+ list.splice(command.payload.index, 1);
215
+ break;
216
+
217
+ case 'reorder':
218
+ list.splice(command.payload.to, 0, ...list.splice(command.payload.from, 1));
219
+ break;
220
+
221
+ default:
222
+ throw new Error('Invalid list command');
223
+ }
224
+
225
+ return list;
226
+ }
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"));
238
+ }
239
+
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);
249
+ }
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
+
260
+ updateList(list, listCommand);
261
+
262
+ for (var [_name, _value] of flatten(list, key)) {
263
+ result.append(_name, _value);
264
+ }
265
+
266
+ return result;
236
267
  }
237
268
 
238
- export { createControlButton, createFieldConfig, getFieldElements, getName, getPaths, isFieldElement, isFieldsetElement, parse, reportValidity, setFieldState, shouldSkipValidate, transform };
269
+ export { applyListCommand, createSubmission, createValidate, flatten, getFormData, getFormElement, getKey, getName, getPaths, isFieldElement, listCommandKey, parseListCommand, serializeListCommand, setFormError, setValue, unflatten, 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.1.0",
5
+ "version": "0.3.0-pre.0",
6
6
  "main": "index.js",
7
7
  "module": "module/index.js",
8
8
  "repository": {