@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 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 FormControl, type Submission, type SubmissionResult } from './submission';
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
- __error?: Error;
15
- __schema?: Schema;
17
+ [error]?: Error;
18
+ [form]?: Schema;
16
19
  };
17
- export type FieldName<FieldSchema, Error = string[], FormSchema extends Record<string, unknown> = Record<string, unknown>> = string & {
18
- __field?: FieldSchema;
19
- __error?: Error;
20
- __form?: FormSchema;
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, Value = Schema> = {
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, Value>;
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, Value>;
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, FormError = string[], Value = Schema> = {
119
+ export type FormContext<Schema extends Record<string, any> = any, FormValue = Schema, FormError = string[]> = {
117
120
  formId: string;
118
- submit(event: SubmitEvent): void;
119
- reset(event: Event): void;
120
- input(event: Event): void;
121
- blur(event: Event): void;
122
- update(options: Partial<FormOptions<Schema, FormError, Value>>): void;
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>, FormError, Value>(options: FormOptions<Schema, FormError, Value>): FormContext<Schema, FormError, Value>;
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.control) {
31
- handleControl(result, lastResult.control);
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 handleControl(meta, control, initialized) {
49
- switch (control.type) {
50
- case 'replace':
48
+ function handleIntent(meta, intent, initialized) {
49
+ switch (intent.type) {
50
+ case 'update':
51
51
  {
52
- var _control$payload$name;
53
- var _name = (_control$payload$name = control.payload.name) !== null && _control$payload$name !== void 0 ? _control$payload$name : '';
54
- var value = control.payload.value;
55
- updateValue(meta, _name, value);
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
- if (typeof control.payload.value === 'undefined' || control.payload.value) {
61
- var _control$payload$name2;
62
- var _name2 = (_control$payload$name2 = control.payload.name) !== null && _control$payload$name2 !== void 0 ? _control$payload$name2 : '';
63
- var _value = formdata.getValue(meta.defaultValue, _name2);
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, control, util.generateId);
76
- submission.setListValue(meta.initialValue, control);
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 => typeof error[name] === 'undefined');
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 _input = document.createElement('input');
262
- _input.type = 'hidden';
263
- _input.name = submission.STATE;
264
- _input.value = '';
265
- form.append(_input);
266
- return _input;
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 input(event) {
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(submission.control.validate({
338
- name: element.name
339
- }));
349
+ dispatch({
350
+ type: 'validate',
351
+ payload: {
352
+ name: element.name
353
+ }
354
+ });
340
355
  }
341
356
  }
342
- function blur(event) {
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(submission.control.validate({
348
- name: element.name
349
- }));
362
+ dispatch({
363
+ type: 'validate',
364
+ payload: {
365
+ name: element.name
366
+ }
367
+ });
350
368
  }
351
- function reset(event) {
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.control) {
380
- handleControl(update, result.control, true);
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 update(options) {
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 (typeof options.lastResult !== 'undefined' && options.lastResult !== currentResult) {
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(control) {
435
+ function dispatch(intent) {
418
436
  var form = getFormElement();
419
437
  var submitter = document.createElement('button');
420
- var buttonProps = getControlButtonProps(control);
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(control) {
447
+ function getControlButtonProps(intent) {
430
448
  return {
431
- name: submission.CONTROL,
432
- value: submission.serializeControl(control),
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
- reset,
443
- input,
444
- blur,
445
- dispatch,
446
- getControlButtonProps,
447
- update,
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