@conform-to/dom 1.0.0-pre.6 → 1.0.0-rc.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 +32 -0
- package/form.d.ts +38 -20
- package/form.js +97 -57
- package/form.mjs +98 -58
- package/formdata.js +2 -3
- package/formdata.mjs +2 -3
- package/index.d.ts +3 -4
- package/index.js +3 -7
- package/index.mjs +3 -4
- package/package.json +1 -1
- package/submission.d.ts +24 -55
- package/submission.js +85 -101
- package/submission.mjs +83 -98
- package/README.md +0 -17
package/README
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
███████╗ ██████╗ ███╗ ██╗ ████████╗ ██████╗ ███████╗ ███╗ ███╗
|
|
4
|
+
██╔═════╝ ██╔═══██╗ ████╗ ██║ ██╔═════╝ ██╔═══██╗ ██╔═══██╗ ████████║
|
|
5
|
+
██║ ██║ ██║ ██╔██╗██║ ███████╗ ██║ ██║ ███████╔╝ ██╔██╔██║
|
|
6
|
+
██║ ██║ ██║ ██║╚████║ ██╔════╝ ██║ ██║ ██╔═══██╗ ██║╚═╝██║
|
|
7
|
+
╚███████╗ ╚██████╔╝ ██║ ╚███║ ██║ ╚██████╔╝ ██║ ██║ ██║ ██║
|
|
8
|
+
╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
Version 1.0.0-rc.0 / License MIT / Copyright (c) 2024 Edmund Hung
|
|
12
|
+
|
|
13
|
+
A type-safe form validation library utilizing web fundamentals to progressively enhance HTML Forms with full support for server frameworks like Remix route action and Next.js server actions.
|
|
14
|
+
|
|
15
|
+
> Getting Started
|
|
16
|
+
|
|
17
|
+
Check out the overview and tutorial at our website https://conform.guide
|
|
18
|
+
|
|
19
|
+
> Documentation
|
|
20
|
+
|
|
21
|
+
The documentation is divided into several sections:
|
|
22
|
+
|
|
23
|
+
* Overview: https://conform.guide/overview
|
|
24
|
+
* Example Usages: https://conform.guide/examples
|
|
25
|
+
* Complex structures: https://conform.guide/complex-structures
|
|
26
|
+
* UI Integrations: https://conform.guide/integrations
|
|
27
|
+
* Accessibility Guide: https://conform.guide/accessibility
|
|
28
|
+
* API Reference: https://conform.guide/references
|
|
29
|
+
|
|
30
|
+
> Support
|
|
31
|
+
|
|
32
|
+
To report a bug, please open an issue on the repository at https://github.com/edmundhung/conform. For feature requests and questions, you can post them in the Discussions section.
|
package/form.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getFormAction, getFormEncType, getFormMethod } from './dom';
|
|
2
|
-
import { type
|
|
2
|
+
import { type Intent, type Submission, type SubmissionResult } from './submission';
|
|
3
3
|
export type UnionKeyof<T> = T extends any ? keyof T : never;
|
|
4
4
|
export type UnionKeyType<T, K extends UnionKeyof<T>> = T extends {
|
|
5
5
|
[k in K]?: any;
|
|
@@ -10,14 +10,17 @@ export type DefaultValue<Schema> = Schema extends string | number | boolean | Da
|
|
|
10
10
|
export type FormValue<Schema> = Schema extends string | number | boolean | Date | null | undefined ? string | undefined : Schema extends File ? File | undefined : Schema extends Array<infer Item> ? Array<FormValue<Item>> | undefined : Schema extends Record<string, any> ? {
|
|
11
11
|
[Key in UnionKeyof<Schema>]?: FormValue<UnionKeyType<Schema, Key>>;
|
|
12
12
|
} | undefined : unknown;
|
|
13
|
+
declare const error: unique symbol;
|
|
14
|
+
declare const field: unique symbol;
|
|
15
|
+
declare const form: unique symbol;
|
|
13
16
|
export type FormId<Schema extends Record<string, unknown> = Record<string, unknown>, Error = string[]> = string & {
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
[error]?: Error;
|
|
18
|
+
[form]?: Schema;
|
|
16
19
|
};
|
|
17
|
-
export type FieldName<FieldSchema,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
export type FieldName<FieldSchema, FormSchema extends Record<string, unknown> = Record<string, unknown>, Error = string[]> = string & {
|
|
21
|
+
[field]?: FieldSchema;
|
|
22
|
+
[error]?: Error;
|
|
23
|
+
[form]?: FormSchema;
|
|
21
24
|
};
|
|
22
25
|
export type Constraint = {
|
|
23
26
|
required?: boolean;
|
|
@@ -43,7 +46,7 @@ export type FormState<FormError> = FormMeta<FormError> & {
|
|
|
43
46
|
valid: Record<string, boolean>;
|
|
44
47
|
dirty: Record<string, boolean>;
|
|
45
48
|
};
|
|
46
|
-
export type FormOptions<Schema, FormError,
|
|
49
|
+
export type FormOptions<Schema, FormError = string[], FormValue = Schema> = {
|
|
47
50
|
/**
|
|
48
51
|
* The id of the form.
|
|
49
52
|
*/
|
|
@@ -59,7 +62,7 @@ export type FormOptions<Schema, FormError, Value = Schema> = {
|
|
|
59
62
|
/**
|
|
60
63
|
* An object describing the result of the last submission
|
|
61
64
|
*/
|
|
62
|
-
lastResult?: SubmissionResult<FormError
|
|
65
|
+
lastResult?: SubmissionResult<FormError> | null;
|
|
63
66
|
/**
|
|
64
67
|
* Define when conform should start validation.
|
|
65
68
|
* Support "onSubmit", "onInput", "onBlur".
|
|
@@ -86,7 +89,7 @@ export type FormOptions<Schema, FormError, Value = Schema> = {
|
|
|
86
89
|
form: HTMLFormElement;
|
|
87
90
|
submitter: HTMLInputElement | HTMLButtonElement | null;
|
|
88
91
|
formData: FormData;
|
|
89
|
-
}) => Submission<Schema, FormError,
|
|
92
|
+
}) => Submission<Schema, FormError, FormValue>;
|
|
90
93
|
/**
|
|
91
94
|
* A function to be called before the form is submitted.
|
|
92
95
|
*/
|
|
@@ -95,7 +98,7 @@ export type FormOptions<Schema, FormError, Value = Schema> = {
|
|
|
95
98
|
action: ReturnType<typeof getFormAction>;
|
|
96
99
|
encType: ReturnType<typeof getFormEncType>;
|
|
97
100
|
method: ReturnType<typeof getFormMethod>;
|
|
98
|
-
submission?: Submission<Schema, FormError,
|
|
101
|
+
submission?: Submission<Schema, FormError, FormValue>;
|
|
99
102
|
}) => void;
|
|
100
103
|
};
|
|
101
104
|
export type SubscriptionSubject = {
|
|
@@ -113,17 +116,32 @@ export type ControlButtonProps = {
|
|
|
113
116
|
form: string;
|
|
114
117
|
formNoValidate: boolean;
|
|
115
118
|
};
|
|
116
|
-
export type FormContext<Schema extends Record<string, any> = any,
|
|
119
|
+
export type FormContext<Schema extends Record<string, any> = any, FormValue = Schema, FormError = string[]> = {
|
|
117
120
|
formId: string;
|
|
118
|
-
submit(event: SubmitEvent):
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
submit(event: SubmitEvent): Submission<Schema, FormError, FormValue> | undefined;
|
|
122
|
+
onReset(event: Event): void;
|
|
123
|
+
onInput(event: Event): void;
|
|
124
|
+
onBlur(event: Event): void;
|
|
125
|
+
onUpdate(options: Partial<FormOptions<Schema, FormError, FormValue>>): void;
|
|
123
126
|
subscribe(callback: () => void, getSubject?: () => SubscriptionSubject | undefined): () => void;
|
|
124
|
-
dispatch(control: FormControl): void;
|
|
125
|
-
getControlButtonProps(control: FormControl): ControlButtonProps;
|
|
126
127
|
getState(): FormState<FormError>;
|
|
127
128
|
getSerializedState(): string;
|
|
129
|
+
} & {
|
|
130
|
+
[Type in Intent['type']]: {} extends Extract<Intent, {
|
|
131
|
+
type: Type;
|
|
132
|
+
}>['payload'] ? (<FieldSchema = Schema>(payload?: Extract<Intent<FieldSchema>, {
|
|
133
|
+
type: Type;
|
|
134
|
+
}>['payload']) => void) & {
|
|
135
|
+
getButtonProps<FieldSchema = Schema>(payload?: Extract<Intent<FieldSchema>, {
|
|
136
|
+
type: Type;
|
|
137
|
+
}>['payload']): ControlButtonProps;
|
|
138
|
+
} : (<FieldSchema = Schema>(payload: Extract<Intent<FieldSchema>, {
|
|
139
|
+
type: Type;
|
|
140
|
+
}>['payload']) => void) & {
|
|
141
|
+
getButtonProps<FieldSchema = Schema>(payload: Extract<Intent<FieldSchema>, {
|
|
142
|
+
type: Type;
|
|
143
|
+
}>['payload']): ControlButtonProps;
|
|
144
|
+
};
|
|
128
145
|
};
|
|
129
|
-
export declare function createFormContext<Schema extends Record<string, any>,
|
|
146
|
+
export declare function createFormContext<Schema extends Record<string, any>, FormValue, FormError>(options: FormOptions<Schema, FormError, FormValue>): FormContext<Schema, FormValue, FormError>;
|
|
147
|
+
export {};
|
package/form.js
CHANGED
|
@@ -27,8 +27,8 @@ function createFormMeta(options, initialized) {
|
|
|
27
27
|
// We can consider adding a warning if it happens
|
|
28
28
|
error: (_ref = lastResult === null || lastResult === void 0 ? void 0 : lastResult.error) !== null && _ref !== void 0 ? _ref : {}
|
|
29
29
|
};
|
|
30
|
-
if (lastResult !== null && lastResult !== void 0 && lastResult.
|
|
31
|
-
|
|
30
|
+
if (lastResult !== null && lastResult !== void 0 && lastResult.intent) {
|
|
31
|
+
handleIntent(result, lastResult.intent);
|
|
32
32
|
}
|
|
33
33
|
return result;
|
|
34
34
|
}
|
|
@@ -45,24 +45,24 @@ function getDefaultKey(defaultValue, prefix) {
|
|
|
45
45
|
return result;
|
|
46
46
|
}, {});
|
|
47
47
|
}
|
|
48
|
-
function
|
|
49
|
-
switch (
|
|
50
|
-
case '
|
|
48
|
+
function handleIntent(meta, intent, initialized) {
|
|
49
|
+
switch (intent.type) {
|
|
50
|
+
case 'update':
|
|
51
51
|
{
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
if (typeof intent.payload.value !== 'undefined') {
|
|
53
|
+
var _intent$payload$name;
|
|
54
|
+
var _name = (_intent$payload$name = intent.payload.name) !== null && _intent$payload$name !== void 0 ? _intent$payload$name : '';
|
|
55
|
+
var value = intent.payload.value;
|
|
56
|
+
updateValue(meta, _name, value);
|
|
57
|
+
}
|
|
56
58
|
break;
|
|
57
59
|
}
|
|
58
60
|
case 'reset':
|
|
59
61
|
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
updateValue(meta, _name2, _value);
|
|
65
|
-
}
|
|
62
|
+
var _intent$payload$name2;
|
|
63
|
+
var _name2 = (_intent$payload$name2 = intent.payload.name) !== null && _intent$payload$name2 !== void 0 ? _intent$payload$name2 : '';
|
|
64
|
+
var _value = formdata.getValue(meta.defaultValue, _name2);
|
|
65
|
+
updateValue(meta, _name2, _value);
|
|
66
66
|
break;
|
|
67
67
|
}
|
|
68
68
|
case 'insert':
|
|
@@ -72,8 +72,8 @@ function handleControl(meta, control, initialized) {
|
|
|
72
72
|
if (initialized) {
|
|
73
73
|
meta.initialValue = util.clone(meta.initialValue);
|
|
74
74
|
meta.key = util.clone(meta.key);
|
|
75
|
-
submission.setListState(meta.key,
|
|
76
|
-
submission.setListValue(meta.initialValue,
|
|
75
|
+
submission.setListState(meta.key, intent, util.generateId);
|
|
76
|
+
submission.setListValue(meta.initialValue, intent);
|
|
77
77
|
}
|
|
78
78
|
break;
|
|
79
79
|
}
|
|
@@ -149,7 +149,18 @@ function createKeyProxy(key) {
|
|
|
149
149
|
});
|
|
150
150
|
}
|
|
151
151
|
function createValidProxy(error) {
|
|
152
|
-
return createStateProxy(name =>
|
|
152
|
+
return createStateProxy(name => {
|
|
153
|
+
var keys = Object.keys(error);
|
|
154
|
+
if (name === '') {
|
|
155
|
+
return keys.length === 0;
|
|
156
|
+
}
|
|
157
|
+
for (var key of keys) {
|
|
158
|
+
if (formdata.isPrefix(key, name) && typeof error[key] !== 'undefined') {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
});
|
|
153
164
|
}
|
|
154
165
|
function createDirtyProxy(defaultValue, value, shouldDirtyConsider) {
|
|
155
166
|
return createStateProxy(name => JSON.stringify(defaultValue[name]) !== JSON.stringify(value[name], (key, value) => {
|
|
@@ -258,12 +269,12 @@ function createFormContext(options) {
|
|
|
258
269
|
var element = form.elements.namedItem(submission.STATE);
|
|
259
270
|
util.invariant(element === null || dom.isFieldElement(element), "The input name \"".concat(submission.STATE, "\" is reserved by Conform. Please use another name."));
|
|
260
271
|
if (!element) {
|
|
261
|
-
var
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
form.append(
|
|
266
|
-
return
|
|
272
|
+
var input = document.createElement('input');
|
|
273
|
+
input.type = 'hidden';
|
|
274
|
+
input.name = submission.STATE;
|
|
275
|
+
input.value = '';
|
|
276
|
+
form.append(input);
|
|
277
|
+
return input;
|
|
267
278
|
}
|
|
268
279
|
return element;
|
|
269
280
|
}
|
|
@@ -290,21 +301,22 @@ function createFormContext(options) {
|
|
|
290
301
|
if (typeof (latestOptions === null || latestOptions === void 0 ? void 0 : latestOptions.onValidate) === 'undefined') {
|
|
291
302
|
var _latestOptions$onSubm;
|
|
292
303
|
(_latestOptions$onSubm = latestOptions.onSubmit) === null || _latestOptions$onSubm === void 0 || _latestOptions$onSubm.call(latestOptions, event, context);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
var submission = latestOptions.onValidate({
|
|
307
|
+
form,
|
|
308
|
+
formData,
|
|
309
|
+
submitter
|
|
310
|
+
});
|
|
311
|
+
if (submission.status !== 'success' && submission.error !== null) {
|
|
312
|
+
report(submission.reply());
|
|
293
313
|
} else {
|
|
294
314
|
var _latestOptions$onSubm2;
|
|
295
|
-
var submission = latestOptions.onValidate({
|
|
296
|
-
form,
|
|
297
|
-
formData,
|
|
298
|
-
submitter
|
|
299
|
-
});
|
|
300
|
-
if (submission.status !== 'success' && submission.error !== null) {
|
|
301
|
-
report(submission.reply());
|
|
302
|
-
event.preventDefault();
|
|
303
|
-
}
|
|
304
315
|
(_latestOptions$onSubm2 = latestOptions.onSubmit) === null || _latestOptions$onSubm2 === void 0 || _latestOptions$onSubm2.call(latestOptions, event, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, context), {}, {
|
|
305
316
|
submission
|
|
306
317
|
}));
|
|
307
318
|
}
|
|
319
|
+
return submission;
|
|
308
320
|
}
|
|
309
321
|
function resolveTarget(event) {
|
|
310
322
|
var form = getFormElement();
|
|
@@ -322,7 +334,7 @@ function createFormContext(options) {
|
|
|
322
334
|
var validated = meta.validated[element.name];
|
|
323
335
|
return validated ? shouldRevalidate === eventName : shouldValidate === eventName;
|
|
324
336
|
}
|
|
325
|
-
function
|
|
337
|
+
function onInput(event) {
|
|
326
338
|
var element = resolveTarget(event);
|
|
327
339
|
if (!element || !element.form) {
|
|
328
340
|
return;
|
|
@@ -334,21 +346,27 @@ function createFormContext(options) {
|
|
|
334
346
|
value: result.payload
|
|
335
347
|
}));
|
|
336
348
|
} else {
|
|
337
|
-
dispatch(
|
|
338
|
-
|
|
339
|
-
|
|
349
|
+
dispatch({
|
|
350
|
+
type: 'validate',
|
|
351
|
+
payload: {
|
|
352
|
+
name: element.name
|
|
353
|
+
}
|
|
354
|
+
});
|
|
340
355
|
}
|
|
341
356
|
}
|
|
342
|
-
function
|
|
357
|
+
function onBlur(event) {
|
|
343
358
|
var element = resolveTarget(event);
|
|
344
359
|
if (!element || event.defaultPrevented || !willValidate(element, 'onBlur')) {
|
|
345
360
|
return;
|
|
346
361
|
}
|
|
347
|
-
dispatch(
|
|
348
|
-
|
|
349
|
-
|
|
362
|
+
dispatch({
|
|
363
|
+
type: 'validate',
|
|
364
|
+
payload: {
|
|
365
|
+
name: element.name
|
|
366
|
+
}
|
|
367
|
+
});
|
|
350
368
|
}
|
|
351
|
-
function
|
|
369
|
+
function onReset(event) {
|
|
352
370
|
var element = getFormElement();
|
|
353
371
|
if (event.type !== 'reset' || event.target !== element || event.defaultPrevented) {
|
|
354
372
|
return;
|
|
@@ -376,8 +394,8 @@ function createFormContext(options) {
|
|
|
376
394
|
error,
|
|
377
395
|
validated: (_result$state$validat = (_result$state = result.state) === null || _result$state === void 0 ? void 0 : _result$state.validated) !== null && _result$state$validat !== void 0 ? _result$state$validat : {}
|
|
378
396
|
});
|
|
379
|
-
if (result.
|
|
380
|
-
|
|
397
|
+
if (result.intent) {
|
|
398
|
+
handleIntent(update, result.intent, true);
|
|
381
399
|
}
|
|
382
400
|
updateFormMeta(update);
|
|
383
401
|
if (result.status === 'error') {
|
|
@@ -389,7 +407,7 @@ function createFormContext(options) {
|
|
|
389
407
|
}
|
|
390
408
|
}
|
|
391
409
|
}
|
|
392
|
-
function
|
|
410
|
+
function onUpdate(options) {
|
|
393
411
|
var currentFormId = latestOptions.formId;
|
|
394
412
|
var currentResult = latestOptions.lastResult;
|
|
395
413
|
|
|
@@ -397,7 +415,7 @@ function createFormContext(options) {
|
|
|
397
415
|
Object.assign(latestOptions, options);
|
|
398
416
|
if (latestOptions.formId !== currentFormId) {
|
|
399
417
|
getFormElement().reset();
|
|
400
|
-
} else if (
|
|
418
|
+
} else if (options.lastResult && options.lastResult !== currentResult) {
|
|
401
419
|
report(options.lastResult);
|
|
402
420
|
}
|
|
403
421
|
}
|
|
@@ -414,10 +432,10 @@ function createFormContext(options) {
|
|
|
414
432
|
function getState() {
|
|
415
433
|
return state;
|
|
416
434
|
}
|
|
417
|
-
function dispatch(
|
|
435
|
+
function dispatch(intent) {
|
|
418
436
|
var form = getFormElement();
|
|
419
437
|
var submitter = document.createElement('button');
|
|
420
|
-
var buttonProps = getControlButtonProps(
|
|
438
|
+
var buttonProps = getControlButtonProps(intent);
|
|
421
439
|
submitter.name = buttonProps.name;
|
|
422
440
|
submitter.value = buttonProps.value;
|
|
423
441
|
submitter.hidden = true;
|
|
@@ -426,25 +444,47 @@ function createFormContext(options) {
|
|
|
426
444
|
dom.requestSubmit(form, submitter);
|
|
427
445
|
form === null || form === void 0 || form.removeChild(submitter);
|
|
428
446
|
}
|
|
429
|
-
function getControlButtonProps(
|
|
447
|
+
function getControlButtonProps(intent) {
|
|
430
448
|
return {
|
|
431
|
-
name: submission.
|
|
432
|
-
value: submission.
|
|
449
|
+
name: submission.INTENT,
|
|
450
|
+
value: submission.serializeIntent(intent),
|
|
433
451
|
form: latestOptions.formId,
|
|
434
452
|
formNoValidate: true
|
|
435
453
|
};
|
|
436
454
|
}
|
|
455
|
+
function createFormControl(type) {
|
|
456
|
+
var control = function control() {
|
|
457
|
+
var payload = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
458
|
+
return dispatch({
|
|
459
|
+
type,
|
|
460
|
+
payload
|
|
461
|
+
});
|
|
462
|
+
};
|
|
463
|
+
return Object.assign(control, {
|
|
464
|
+
getButtonProps() {
|
|
465
|
+
var payload = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
466
|
+
return getControlButtonProps({
|
|
467
|
+
type,
|
|
468
|
+
payload
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
437
473
|
return {
|
|
438
474
|
get formId() {
|
|
439
475
|
return latestOptions.formId;
|
|
440
476
|
},
|
|
441
477
|
submit,
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
478
|
+
onReset,
|
|
479
|
+
onInput,
|
|
480
|
+
onBlur,
|
|
481
|
+
onUpdate,
|
|
482
|
+
validate: createFormControl('validate'),
|
|
483
|
+
reset: createFormControl('reset'),
|
|
484
|
+
update: createFormControl('update'),
|
|
485
|
+
insert: createFormControl('insert'),
|
|
486
|
+
remove: createFormControl('remove'),
|
|
487
|
+
reorder: createFormControl('reorder'),
|
|
448
488
|
subscribe,
|
|
449
489
|
getState,
|
|
450
490
|
getSerializedState
|