@conform-to/dom 0.6.0-pre.0 → 0.6.1
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 +10 -4
- package/index.d.ts +24 -12
- package/index.js +46 -65
- package/module/index.js +43 -62
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
> This package is a transitive dependency for the rest of the conform packages with no intention to be used directly at the moment. Use at your own risk.
|
|
4
4
|
|
|
5
|
-
Conform is a form validation library
|
|
5
|
+
Conform is a progressive enhancement first form validation library for [Remix](https://remix.run)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
7
|
+
### Highlights
|
|
8
|
+
|
|
9
|
+
- Focused on progressive enhancment by default
|
|
10
|
+
- Simplifed intergration through event delegation
|
|
11
|
+
- Server first validation with Zod / Yup schema support
|
|
12
|
+
- Field name inference with type checking
|
|
13
|
+
- Focus management
|
|
14
|
+
- Accessibility support
|
|
15
|
+
- About 5kb compressed
|
|
10
16
|
|
|
11
17
|
Checkout the [repository](https://github.com/edmundhung/conform) if you want to know more!
|
package/index.d.ts
CHANGED
|
@@ -6,7 +6,16 @@ export interface FieldConfig<Schema = unknown> extends FieldConstraint<Schema> {
|
|
|
6
6
|
defaultValue?: FieldValue<Schema>;
|
|
7
7
|
initialError?: Record<string, string | string[]>;
|
|
8
8
|
form?: string;
|
|
9
|
+
descriptionId?: string;
|
|
9
10
|
errorId?: string;
|
|
11
|
+
/**
|
|
12
|
+
* The frist error of the field
|
|
13
|
+
*/
|
|
14
|
+
error?: string;
|
|
15
|
+
/**
|
|
16
|
+
* All of the field errors
|
|
17
|
+
*/
|
|
18
|
+
errors?: string[];
|
|
10
19
|
}
|
|
11
20
|
export type FieldValue<Schema> = Schema extends Primitive ? string : Schema extends File ? File : Schema extends Array<infer InnerType> ? Array<FieldValue<InnerType>> : Schema extends Record<string, any> ? {
|
|
12
21
|
[Key in keyof Schema]?: FieldValue<Schema[Key]>;
|
|
@@ -36,12 +45,19 @@ export type Submission<Schema extends Record<string, any> | unknown = unknown> =
|
|
|
36
45
|
toJSON(): Submission;
|
|
37
46
|
};
|
|
38
47
|
export interface IntentButtonProps {
|
|
39
|
-
name:
|
|
48
|
+
name: typeof INTENT;
|
|
40
49
|
value: string;
|
|
41
50
|
formNoValidate?: boolean;
|
|
42
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if the provided reference is a form element (_input_ / _select_ / _textarea_ or _button_)
|
|
54
|
+
*/
|
|
43
55
|
export declare function isFieldElement(element: unknown): element is FieldElement;
|
|
44
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Find the corresponding paths based on the formatted name
|
|
58
|
+
* @param name formatted name
|
|
59
|
+
* @returns paths
|
|
60
|
+
*/
|
|
45
61
|
export declare function getPaths(name: string): Array<string | number>;
|
|
46
62
|
export declare function getFormData(form: HTMLFormElement, submitter?: HTMLInputElement | HTMLButtonElement | null): FormData;
|
|
47
63
|
export type FormMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
|
|
@@ -52,19 +68,16 @@ export declare function getFormAttributes(form: HTMLFormElement, submitter?: HTM
|
|
|
52
68
|
method: FormMethod;
|
|
53
69
|
};
|
|
54
70
|
export declare function getName(paths: Array<string | number>): string;
|
|
55
|
-
export declare function
|
|
71
|
+
export declare function getScope(intent: string): string | null;
|
|
72
|
+
export declare function isFocusedOnIntentButton(form: HTMLFormElement, intent: string): boolean;
|
|
56
73
|
export declare function getValidationMessage(errors?: string | string[]): string;
|
|
57
74
|
export declare function getErrors(message: string | undefined): string[];
|
|
75
|
+
export declare const FORM_ERROR_ELEMENT_NAME = "__form__";
|
|
76
|
+
export declare const INTENT = "__intent__";
|
|
58
77
|
export declare const VALIDATION_UNDEFINED = "__undefined__";
|
|
59
78
|
export declare const VALIDATION_SKIPPED = "__skipped__";
|
|
60
79
|
export declare function reportSubmission(form: HTMLFormElement, submission: Submission): void;
|
|
61
80
|
export declare function setValue<T>(target: any, paths: Array<string | number>, valueFn: (prev?: T) => T): void;
|
|
62
|
-
/**
|
|
63
|
-
* The ponyfill of `HTMLFormElement.requestSubmit()`
|
|
64
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit
|
|
65
|
-
* @see https://caniuse.com/?search=requestSubmit
|
|
66
|
-
*/
|
|
67
|
-
export declare function requestSubmit(form: HTMLFormElement, submitter?: HTMLButtonElement | HTMLInputElement): void;
|
|
68
81
|
/**
|
|
69
82
|
* Creates an intent button on demand and trigger a form submit by clicking it.
|
|
70
83
|
*/
|
|
@@ -73,13 +86,12 @@ export declare function requestIntent(form: HTMLFormElement | undefined, buttonP
|
|
|
73
86
|
formNoValidate?: boolean;
|
|
74
87
|
}): void;
|
|
75
88
|
/**
|
|
76
|
-
* Returns the properties required to configure
|
|
89
|
+
* Returns the properties required to configure an intent button for validation
|
|
77
90
|
*
|
|
78
91
|
* @see https://conform.guide/api/react#validate
|
|
79
92
|
*/
|
|
80
93
|
export declare function validate(field?: string): IntentButtonProps;
|
|
81
94
|
export declare function getFormElement(element: HTMLFormElement | HTMLFieldSetElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement | null): HTMLFormElement | null;
|
|
82
|
-
export declare function focus(field: FieldElement): void;
|
|
83
95
|
export declare function parse(payload: FormData | URLSearchParams): Submission;
|
|
84
96
|
export declare function parse<Schema>(payload: FormData | URLSearchParams, options?: {
|
|
85
97
|
resolve?: (payload: Record<string, any>, intent: string) => {
|
|
@@ -161,7 +173,7 @@ export interface ListCommandButtonBuilder {
|
|
|
161
173
|
}): IntentButtonProps;
|
|
162
174
|
}
|
|
163
175
|
/**
|
|
164
|
-
* Helpers to configure
|
|
176
|
+
* Helpers to configure an intent button for modifying a list
|
|
165
177
|
*
|
|
166
178
|
* @see https://conform.guide/api/react#list
|
|
167
179
|
*/
|
package/index.js
CHANGED
|
@@ -4,27 +4,18 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// : never : never;
|
|
11
|
-
|
|
12
|
-
// type DottedPaths<T> = T extends object ?
|
|
13
|
-
// { [K in keyof T]-?: K extends string | number ?
|
|
14
|
-
// `${K}` | Join<K, DottedPaths<T[K]>>
|
|
15
|
-
// : never
|
|
16
|
-
// }[keyof T] : ""
|
|
17
|
-
|
|
18
|
-
// type Pathfix<T> = T extends `${infer Prefix}.${number}${infer Postfix}` ? `${Prefix}[${number}]${Pathfix<Postfix>}` : T;
|
|
19
|
-
|
|
20
|
-
// type Path<Schema> = Pathfix<DottedPaths<Schema>> | '';
|
|
21
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Check if the provided reference is a form element (_input_ / _select_ / _textarea_ or _button_)
|
|
9
|
+
*/
|
|
22
10
|
function isFieldElement(element) {
|
|
23
11
|
return element instanceof Element && (element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA' || element.tagName === 'BUTTON');
|
|
24
12
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Find the corresponding paths based on the formatted name
|
|
16
|
+
* @param name formatted name
|
|
17
|
+
* @returns paths
|
|
18
|
+
*/
|
|
28
19
|
function getPaths(name) {
|
|
29
20
|
var pattern = /(\w*)\[(\d+)\]/;
|
|
30
21
|
if (!name) {
|
|
@@ -71,18 +62,22 @@ function getName(paths) {
|
|
|
71
62
|
return [name, path].join('.');
|
|
72
63
|
}, '');
|
|
73
64
|
}
|
|
74
|
-
function
|
|
75
|
-
var _parseListCommand;
|
|
76
|
-
var [type] = intent.split('/'
|
|
65
|
+
function getScope(intent) {
|
|
66
|
+
var _parseListCommand$sco, _parseListCommand;
|
|
67
|
+
var [type, ...rest] = intent.split('/');
|
|
77
68
|
switch (type) {
|
|
78
69
|
case 'validate':
|
|
79
|
-
return
|
|
70
|
+
return rest.length > 0 ? rest.join('/') : null;
|
|
80
71
|
case 'list':
|
|
81
|
-
return ((_parseListCommand = parseListCommand(intent)) === null || _parseListCommand === void 0 ? void 0 : _parseListCommand.scope)
|
|
72
|
+
return (_parseListCommand$sco = (_parseListCommand = parseListCommand(intent)) === null || _parseListCommand === void 0 ? void 0 : _parseListCommand.scope) !== null && _parseListCommand$sco !== void 0 ? _parseListCommand$sco : null;
|
|
82
73
|
default:
|
|
83
|
-
return
|
|
74
|
+
return null;
|
|
84
75
|
}
|
|
85
76
|
}
|
|
77
|
+
function isFocusedOnIntentButton(form, intent) {
|
|
78
|
+
var element = document.activeElement;
|
|
79
|
+
return isFieldElement(element) && element.tagName === 'BUTTON' && element.form === form && element.name === INTENT && element.value === intent;
|
|
80
|
+
}
|
|
86
81
|
function getValidationMessage(errors) {
|
|
87
82
|
return [].concat(errors !== null && errors !== void 0 ? errors : []).join(String.fromCharCode(31));
|
|
88
83
|
}
|
|
@@ -92,6 +87,8 @@ function getErrors(message) {
|
|
|
92
87
|
}
|
|
93
88
|
return message.split(String.fromCharCode(31));
|
|
94
89
|
}
|
|
90
|
+
var FORM_ERROR_ELEMENT_NAME = '__form__';
|
|
91
|
+
var INTENT = '__intent__';
|
|
95
92
|
var VALIDATION_UNDEFINED = '__undefined__';
|
|
96
93
|
var VALIDATION_SKIPPED = '__skipped__';
|
|
97
94
|
function reportSubmission(form, submission) {
|
|
@@ -103,7 +100,7 @@ function reportSubmission(form, submission) {
|
|
|
103
100
|
|
|
104
101
|
// We can't use empty string as button name
|
|
105
102
|
// As `form.element.namedItem('')` will always returns null
|
|
106
|
-
var elementName = _name ? _name :
|
|
103
|
+
var elementName = _name ? _name : FORM_ERROR_ELEMENT_NAME;
|
|
107
104
|
var item = form.elements.namedItem(elementName);
|
|
108
105
|
if (item instanceof RadioNodeList) {
|
|
109
106
|
for (var field of item) {
|
|
@@ -122,23 +119,28 @@ function reportSubmission(form, submission) {
|
|
|
122
119
|
form.appendChild(button);
|
|
123
120
|
}
|
|
124
121
|
}
|
|
122
|
+
var focusedFirstInvalidField = false;
|
|
123
|
+
var scope = getScope(submission.intent);
|
|
124
|
+
var isSubmitting = submission.intent.slice(0, submission.intent.indexOf('/')) !== 'validate' && parseListCommand(submission.intent) === null;
|
|
125
125
|
for (var element of form.elements) {
|
|
126
126
|
if (isFieldElement(element) && element.willValidate) {
|
|
127
|
-
var
|
|
128
|
-
var
|
|
129
|
-
var
|
|
130
|
-
|
|
127
|
+
var _submission$error$_el;
|
|
128
|
+
var _elementName = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
|
|
129
|
+
var messages = [].concat((_submission$error$_el = submission.error[_elementName]) !== null && _submission$error$_el !== void 0 ? _submission$error$_el : []);
|
|
130
|
+
var shouldValidate = scope === null || scope === _elementName;
|
|
131
|
+
if (shouldValidate) {
|
|
131
132
|
element.dataset.conformTouched = 'true';
|
|
132
133
|
}
|
|
133
|
-
if (
|
|
134
|
+
if (!messages.includes(VALIDATION_SKIPPED) && !messages.includes(VALIDATION_UNDEFINED)) {
|
|
134
135
|
var invalidEvent = new Event('invalid', {
|
|
135
136
|
cancelable: true
|
|
136
137
|
});
|
|
137
|
-
element.setCustomValidity(getValidationMessage(
|
|
138
|
+
element.setCustomValidity(getValidationMessage(messages));
|
|
138
139
|
element.dispatchEvent(invalidEvent);
|
|
139
140
|
}
|
|
140
|
-
if (
|
|
141
|
-
focus(
|
|
141
|
+
if (!focusedFirstInvalidField && (isSubmitting || isFocusedOnIntentButton(form, submission.intent)) && shouldValidate && element.tagName !== 'BUTTON' && !element.validity.valid) {
|
|
142
|
+
element.focus();
|
|
143
|
+
focusedFirstInvalidField = true;
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
}
|
|
@@ -158,20 +160,6 @@ function setValue(target, paths, valueFn) {
|
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
162
|
|
|
161
|
-
/**
|
|
162
|
-
* The ponyfill of `HTMLFormElement.requestSubmit()`
|
|
163
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit
|
|
164
|
-
* @see https://caniuse.com/?search=requestSubmit
|
|
165
|
-
*/
|
|
166
|
-
function requestSubmit(form, submitter) {
|
|
167
|
-
var submitEvent = new SubmitEvent('submit', {
|
|
168
|
-
bubbles: true,
|
|
169
|
-
cancelable: true,
|
|
170
|
-
submitter
|
|
171
|
-
});
|
|
172
|
-
form.dispatchEvent(submitEvent);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
163
|
/**
|
|
176
164
|
* Creates an intent button on demand and trigger a form submit by clicking it.
|
|
177
165
|
*/
|
|
@@ -181,7 +169,7 @@ function requestIntent(form, buttonProps) {
|
|
|
181
169
|
return;
|
|
182
170
|
}
|
|
183
171
|
var button = document.createElement('button');
|
|
184
|
-
button.name =
|
|
172
|
+
button.name = INTENT;
|
|
185
173
|
button.value = buttonProps.value;
|
|
186
174
|
button.hidden = true;
|
|
187
175
|
if (buttonProps.formNoValidate) {
|
|
@@ -193,13 +181,13 @@ function requestIntent(form, buttonProps) {
|
|
|
193
181
|
}
|
|
194
182
|
|
|
195
183
|
/**
|
|
196
|
-
* Returns the properties required to configure
|
|
184
|
+
* Returns the properties required to configure an intent button for validation
|
|
197
185
|
*
|
|
198
186
|
* @see https://conform.guide/api/react#validate
|
|
199
187
|
*/
|
|
200
188
|
function validate(field) {
|
|
201
189
|
return {
|
|
202
|
-
name:
|
|
190
|
+
name: INTENT,
|
|
203
191
|
value: field ? "validate/".concat(field) : 'validate',
|
|
204
192
|
formNoValidate: true
|
|
205
193
|
};
|
|
@@ -211,13 +199,6 @@ function getFormElement(element) {
|
|
|
211
199
|
}
|
|
212
200
|
return form;
|
|
213
201
|
}
|
|
214
|
-
function focus(field) {
|
|
215
|
-
var currentFocus = document.activeElement;
|
|
216
|
-
if (!isFieldElement(currentFocus) || currentFocus.tagName !== 'BUTTON' || currentFocus.form !== field.form) {
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
field.focus();
|
|
220
|
-
}
|
|
221
202
|
function parse(payload, options) {
|
|
222
203
|
var submission = {
|
|
223
204
|
intent: 'submit',
|
|
@@ -225,7 +206,7 @@ function parse(payload, options) {
|
|
|
225
206
|
error: {}
|
|
226
207
|
};
|
|
227
208
|
var _loop = function _loop(_value) {
|
|
228
|
-
if (_name2 ===
|
|
209
|
+
if (_name2 === INTENT) {
|
|
229
210
|
if (typeof _value !== 'string' || submission.intent !== 'submit') {
|
|
230
211
|
throw new Error('The intent could only be set on a button');
|
|
231
212
|
}
|
|
@@ -323,7 +304,7 @@ function updateList(list, command) {
|
|
|
323
304
|
return list;
|
|
324
305
|
}
|
|
325
306
|
/**
|
|
326
|
-
* Helpers to configure
|
|
307
|
+
* Helpers to configure an intent button for modifying a list
|
|
327
308
|
*
|
|
328
309
|
* @see https://conform.guide/api/react#list
|
|
329
310
|
*/
|
|
@@ -338,7 +319,7 @@ var list = new Proxy({}, {
|
|
|
338
319
|
return function (scope) {
|
|
339
320
|
var payload = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
340
321
|
return {
|
|
341
|
-
name:
|
|
322
|
+
name: INTENT,
|
|
342
323
|
value: "list/".concat(type, "/").concat(scope, "/").concat(JSON.stringify(payload)),
|
|
343
324
|
formNoValidate: true
|
|
344
325
|
};
|
|
@@ -384,7 +365,7 @@ function validateConstraint(options) {
|
|
|
384
365
|
var _loop2 = function _loop2(element) {
|
|
385
366
|
if (isFieldElement(element)) {
|
|
386
367
|
var _options$acceptMultip, _options$acceptMultip2;
|
|
387
|
-
var _name3 = element.name
|
|
368
|
+
var _name3 = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
|
|
388
369
|
var constraint = Object.entries(element.dataset).reduce((result, _ref4) => {
|
|
389
370
|
var [name, attributeValue = ''] = _ref4;
|
|
390
371
|
if (constraintPattern.test(name)) {
|
|
@@ -428,26 +409,26 @@ function validateConstraint(options) {
|
|
|
428
409
|
});
|
|
429
410
|
}
|
|
430
411
|
|
|
412
|
+
exports.FORM_ERROR_ELEMENT_NAME = FORM_ERROR_ELEMENT_NAME;
|
|
413
|
+
exports.INTENT = INTENT;
|
|
431
414
|
exports.VALIDATION_SKIPPED = VALIDATION_SKIPPED;
|
|
432
415
|
exports.VALIDATION_UNDEFINED = VALIDATION_UNDEFINED;
|
|
433
|
-
exports.focus = focus;
|
|
434
416
|
exports.getErrors = getErrors;
|
|
435
417
|
exports.getFormAttributes = getFormAttributes;
|
|
436
418
|
exports.getFormData = getFormData;
|
|
437
419
|
exports.getFormElement = getFormElement;
|
|
438
|
-
exports.getFormElements = getFormElements;
|
|
439
420
|
exports.getName = getName;
|
|
440
421
|
exports.getPaths = getPaths;
|
|
422
|
+
exports.getScope = getScope;
|
|
441
423
|
exports.getValidationMessage = getValidationMessage;
|
|
442
424
|
exports.isFieldElement = isFieldElement;
|
|
425
|
+
exports.isFocusedOnIntentButton = isFocusedOnIntentButton;
|
|
443
426
|
exports.list = list;
|
|
444
427
|
exports.parse = parse;
|
|
445
428
|
exports.parseListCommand = parseListCommand;
|
|
446
429
|
exports.reportSubmission = reportSubmission;
|
|
447
430
|
exports.requestIntent = requestIntent;
|
|
448
|
-
exports.requestSubmit = requestSubmit;
|
|
449
431
|
exports.setValue = setValue;
|
|
450
|
-
exports.shouldValidate = shouldValidate;
|
|
451
432
|
exports.updateList = updateList;
|
|
452
433
|
exports.validate = validate;
|
|
453
434
|
exports.validateConstraint = validateConstraint;
|
package/module/index.js
CHANGED
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// : never : never;
|
|
7
|
-
|
|
8
|
-
// type DottedPaths<T> = T extends object ?
|
|
9
|
-
// { [K in keyof T]-?: K extends string | number ?
|
|
10
|
-
// `${K}` | Join<K, DottedPaths<T[K]>>
|
|
11
|
-
// : never
|
|
12
|
-
// }[keyof T] : ""
|
|
13
|
-
|
|
14
|
-
// type Pathfix<T> = T extends `${infer Prefix}.${number}${infer Postfix}` ? `${Prefix}[${number}]${Pathfix<Postfix>}` : T;
|
|
15
|
-
|
|
16
|
-
// type Path<Schema> = Pathfix<DottedPaths<Schema>> | '';
|
|
17
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Check if the provided reference is a form element (_input_ / _select_ / _textarea_ or _button_)
|
|
5
|
+
*/
|
|
18
6
|
function isFieldElement(element) {
|
|
19
7
|
return element instanceof Element && (element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA' || element.tagName === 'BUTTON');
|
|
20
8
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Find the corresponding paths based on the formatted name
|
|
12
|
+
* @param name formatted name
|
|
13
|
+
* @returns paths
|
|
14
|
+
*/
|
|
24
15
|
function getPaths(name) {
|
|
25
16
|
var pattern = /(\w*)\[(\d+)\]/;
|
|
26
17
|
if (!name) {
|
|
@@ -67,18 +58,22 @@ function getName(paths) {
|
|
|
67
58
|
return [name, path].join('.');
|
|
68
59
|
}, '');
|
|
69
60
|
}
|
|
70
|
-
function
|
|
71
|
-
var _parseListCommand;
|
|
72
|
-
var [type] = intent.split('/'
|
|
61
|
+
function getScope(intent) {
|
|
62
|
+
var _parseListCommand$sco, _parseListCommand;
|
|
63
|
+
var [type, ...rest] = intent.split('/');
|
|
73
64
|
switch (type) {
|
|
74
65
|
case 'validate':
|
|
75
|
-
return
|
|
66
|
+
return rest.length > 0 ? rest.join('/') : null;
|
|
76
67
|
case 'list':
|
|
77
|
-
return ((_parseListCommand = parseListCommand(intent)) === null || _parseListCommand === void 0 ? void 0 : _parseListCommand.scope)
|
|
68
|
+
return (_parseListCommand$sco = (_parseListCommand = parseListCommand(intent)) === null || _parseListCommand === void 0 ? void 0 : _parseListCommand.scope) !== null && _parseListCommand$sco !== void 0 ? _parseListCommand$sco : null;
|
|
78
69
|
default:
|
|
79
|
-
return
|
|
70
|
+
return null;
|
|
80
71
|
}
|
|
81
72
|
}
|
|
73
|
+
function isFocusedOnIntentButton(form, intent) {
|
|
74
|
+
var element = document.activeElement;
|
|
75
|
+
return isFieldElement(element) && element.tagName === 'BUTTON' && element.form === form && element.name === INTENT && element.value === intent;
|
|
76
|
+
}
|
|
82
77
|
function getValidationMessage(errors) {
|
|
83
78
|
return [].concat(errors !== null && errors !== void 0 ? errors : []).join(String.fromCharCode(31));
|
|
84
79
|
}
|
|
@@ -88,6 +83,8 @@ function getErrors(message) {
|
|
|
88
83
|
}
|
|
89
84
|
return message.split(String.fromCharCode(31));
|
|
90
85
|
}
|
|
86
|
+
var FORM_ERROR_ELEMENT_NAME = '__form__';
|
|
87
|
+
var INTENT = '__intent__';
|
|
91
88
|
var VALIDATION_UNDEFINED = '__undefined__';
|
|
92
89
|
var VALIDATION_SKIPPED = '__skipped__';
|
|
93
90
|
function reportSubmission(form, submission) {
|
|
@@ -99,7 +96,7 @@ function reportSubmission(form, submission) {
|
|
|
99
96
|
|
|
100
97
|
// We can't use empty string as button name
|
|
101
98
|
// As `form.element.namedItem('')` will always returns null
|
|
102
|
-
var elementName = _name ? _name :
|
|
99
|
+
var elementName = _name ? _name : FORM_ERROR_ELEMENT_NAME;
|
|
103
100
|
var item = form.elements.namedItem(elementName);
|
|
104
101
|
if (item instanceof RadioNodeList) {
|
|
105
102
|
for (var field of item) {
|
|
@@ -118,23 +115,28 @@ function reportSubmission(form, submission) {
|
|
|
118
115
|
form.appendChild(button);
|
|
119
116
|
}
|
|
120
117
|
}
|
|
118
|
+
var focusedFirstInvalidField = false;
|
|
119
|
+
var scope = getScope(submission.intent);
|
|
120
|
+
var isSubmitting = submission.intent.slice(0, submission.intent.indexOf('/')) !== 'validate' && parseListCommand(submission.intent) === null;
|
|
121
121
|
for (var element of form.elements) {
|
|
122
122
|
if (isFieldElement(element) && element.willValidate) {
|
|
123
|
-
var
|
|
124
|
-
var
|
|
125
|
-
var
|
|
126
|
-
|
|
123
|
+
var _submission$error$_el;
|
|
124
|
+
var _elementName = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
|
|
125
|
+
var messages = [].concat((_submission$error$_el = submission.error[_elementName]) !== null && _submission$error$_el !== void 0 ? _submission$error$_el : []);
|
|
126
|
+
var shouldValidate = scope === null || scope === _elementName;
|
|
127
|
+
if (shouldValidate) {
|
|
127
128
|
element.dataset.conformTouched = 'true';
|
|
128
129
|
}
|
|
129
|
-
if (
|
|
130
|
+
if (!messages.includes(VALIDATION_SKIPPED) && !messages.includes(VALIDATION_UNDEFINED)) {
|
|
130
131
|
var invalidEvent = new Event('invalid', {
|
|
131
132
|
cancelable: true
|
|
132
133
|
});
|
|
133
|
-
element.setCustomValidity(getValidationMessage(
|
|
134
|
+
element.setCustomValidity(getValidationMessage(messages));
|
|
134
135
|
element.dispatchEvent(invalidEvent);
|
|
135
136
|
}
|
|
136
|
-
if (
|
|
137
|
-
focus(
|
|
137
|
+
if (!focusedFirstInvalidField && (isSubmitting || isFocusedOnIntentButton(form, submission.intent)) && shouldValidate && element.tagName !== 'BUTTON' && !element.validity.valid) {
|
|
138
|
+
element.focus();
|
|
139
|
+
focusedFirstInvalidField = true;
|
|
138
140
|
}
|
|
139
141
|
}
|
|
140
142
|
}
|
|
@@ -154,20 +156,6 @@ function setValue(target, paths, valueFn) {
|
|
|
154
156
|
}
|
|
155
157
|
}
|
|
156
158
|
|
|
157
|
-
/**
|
|
158
|
-
* The ponyfill of `HTMLFormElement.requestSubmit()`
|
|
159
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit
|
|
160
|
-
* @see https://caniuse.com/?search=requestSubmit
|
|
161
|
-
*/
|
|
162
|
-
function requestSubmit(form, submitter) {
|
|
163
|
-
var submitEvent = new SubmitEvent('submit', {
|
|
164
|
-
bubbles: true,
|
|
165
|
-
cancelable: true,
|
|
166
|
-
submitter
|
|
167
|
-
});
|
|
168
|
-
form.dispatchEvent(submitEvent);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
159
|
/**
|
|
172
160
|
* Creates an intent button on demand and trigger a form submit by clicking it.
|
|
173
161
|
*/
|
|
@@ -177,7 +165,7 @@ function requestIntent(form, buttonProps) {
|
|
|
177
165
|
return;
|
|
178
166
|
}
|
|
179
167
|
var button = document.createElement('button');
|
|
180
|
-
button.name =
|
|
168
|
+
button.name = INTENT;
|
|
181
169
|
button.value = buttonProps.value;
|
|
182
170
|
button.hidden = true;
|
|
183
171
|
if (buttonProps.formNoValidate) {
|
|
@@ -189,13 +177,13 @@ function requestIntent(form, buttonProps) {
|
|
|
189
177
|
}
|
|
190
178
|
|
|
191
179
|
/**
|
|
192
|
-
* Returns the properties required to configure
|
|
180
|
+
* Returns the properties required to configure an intent button for validation
|
|
193
181
|
*
|
|
194
182
|
* @see https://conform.guide/api/react#validate
|
|
195
183
|
*/
|
|
196
184
|
function validate(field) {
|
|
197
185
|
return {
|
|
198
|
-
name:
|
|
186
|
+
name: INTENT,
|
|
199
187
|
value: field ? "validate/".concat(field) : 'validate',
|
|
200
188
|
formNoValidate: true
|
|
201
189
|
};
|
|
@@ -207,13 +195,6 @@ function getFormElement(element) {
|
|
|
207
195
|
}
|
|
208
196
|
return form;
|
|
209
197
|
}
|
|
210
|
-
function focus(field) {
|
|
211
|
-
var currentFocus = document.activeElement;
|
|
212
|
-
if (!isFieldElement(currentFocus) || currentFocus.tagName !== 'BUTTON' || currentFocus.form !== field.form) {
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
field.focus();
|
|
216
|
-
}
|
|
217
198
|
function parse(payload, options) {
|
|
218
199
|
var submission = {
|
|
219
200
|
intent: 'submit',
|
|
@@ -221,7 +202,7 @@ function parse(payload, options) {
|
|
|
221
202
|
error: {}
|
|
222
203
|
};
|
|
223
204
|
var _loop = function _loop(_value) {
|
|
224
|
-
if (_name2 ===
|
|
205
|
+
if (_name2 === INTENT) {
|
|
225
206
|
if (typeof _value !== 'string' || submission.intent !== 'submit') {
|
|
226
207
|
throw new Error('The intent could only be set on a button');
|
|
227
208
|
}
|
|
@@ -319,7 +300,7 @@ function updateList(list, command) {
|
|
|
319
300
|
return list;
|
|
320
301
|
}
|
|
321
302
|
/**
|
|
322
|
-
* Helpers to configure
|
|
303
|
+
* Helpers to configure an intent button for modifying a list
|
|
323
304
|
*
|
|
324
305
|
* @see https://conform.guide/api/react#list
|
|
325
306
|
*/
|
|
@@ -334,7 +315,7 @@ var list = new Proxy({}, {
|
|
|
334
315
|
return function (scope) {
|
|
335
316
|
var payload = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
336
317
|
return {
|
|
337
|
-
name:
|
|
318
|
+
name: INTENT,
|
|
338
319
|
value: "list/".concat(type, "/").concat(scope, "/").concat(JSON.stringify(payload)),
|
|
339
320
|
formNoValidate: true
|
|
340
321
|
};
|
|
@@ -380,7 +361,7 @@ function validateConstraint(options) {
|
|
|
380
361
|
var _loop2 = function _loop2(element) {
|
|
381
362
|
if (isFieldElement(element)) {
|
|
382
363
|
var _options$acceptMultip, _options$acceptMultip2;
|
|
383
|
-
var _name3 = element.name
|
|
364
|
+
var _name3 = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
|
|
384
365
|
var constraint = Object.entries(element.dataset).reduce((result, _ref4) => {
|
|
385
366
|
var [name, attributeValue = ''] = _ref4;
|
|
386
367
|
if (constraintPattern.test(name)) {
|
|
@@ -424,4 +405,4 @@ function validateConstraint(options) {
|
|
|
424
405
|
});
|
|
425
406
|
}
|
|
426
407
|
|
|
427
|
-
export { VALIDATION_SKIPPED, VALIDATION_UNDEFINED,
|
|
408
|
+
export { FORM_ERROR_ELEMENT_NAME, INTENT, VALIDATION_SKIPPED, VALIDATION_UNDEFINED, getErrors, getFormAttributes, getFormData, getFormElement, getName, getPaths, getScope, getValidationMessage, isFieldElement, isFocusedOnIntentButton, list, parse, parseListCommand, reportSubmission, requestIntent, setValue, updateList, validate, validateConstraint };
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conform-to/dom",
|
|
3
3
|
"description": "A set of opinionated helpers built on top of the Constraint Validation API",
|
|
4
|
+
"homepage": "https://conform.guide",
|
|
4
5
|
"license": "MIT",
|
|
5
|
-
"version": "0.6.
|
|
6
|
+
"version": "0.6.1",
|
|
6
7
|
"main": "index.js",
|
|
7
8
|
"module": "module/index.js",
|
|
8
9
|
"repository": {
|
|
@@ -22,6 +23,8 @@
|
|
|
22
23
|
"constraint-validation",
|
|
23
24
|
"form",
|
|
24
25
|
"form-validation",
|
|
26
|
+
"html",
|
|
27
|
+
"progressive-enhancement",
|
|
25
28
|
"validation",
|
|
26
29
|
"dom"
|
|
27
30
|
],
|