@conform-to/react 0.7.0-pre.1 → 0.7.0-pre.2

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
@@ -507,7 +507,7 @@ export default function SignupForm() {
507
507
 
508
508
  ### list
509
509
 
510
- It provides serveral helpers to configure an intent button for [modifying a list](/docs/commands.md#modifying-a-list).
510
+ It provides serveral helpers to configure an intent button for [modifying a list](/docs/intent-button.md#modifying-a-list).
511
511
 
512
512
  ```tsx
513
513
  import { list } from '@conform-to/react';
@@ -540,7 +540,7 @@ function Example() {
540
540
 
541
541
  ### validate
542
542
 
543
- It returns the properties required to configure an intent button for [validation](/docs/commands.md#validation).
543
+ It returns the properties required to configure an intent button for [validation](/docs/intent-button.md#validation).
544
544
 
545
545
  ```tsx
546
546
  import { validate } from '@conform-to/react';
@@ -562,7 +562,7 @@ function Example() {
562
562
 
563
563
  ### requestIntent
564
564
 
565
- It lets you [trigger an intent](/docs/commands.md#triggering-an-intent) without requiring users to click on a button. It supports both [list](#list) and [validate](#validate) intent.
565
+ It lets you [trigger an intent](/docs/intent-button.md#triggering-an-intent) without requiring users to click on a button. It supports both [list](#list) and [validate](#validate) intent.
566
566
 
567
567
  ```tsx
568
568
  import {
package/hooks.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type FieldConstraint, type FieldElement, type FieldsetConstraint, type Submission, type KeysOf, type ResolveType, getFormEncType, getFormMethod } from '@conform-to/dom';
1
+ import { type FieldConstraint, type FieldElement, type FieldsetConstraint, type Submission, type KeysOf, type ResolveType, getFormEncType, getFormMethod, parseIntent } from '@conform-to/dom';
2
2
  import { type FormEvent, type RefObject } from 'react';
3
3
  export type Primitive = null | undefined | string | number | boolean | Date;
4
4
  export interface FieldConfig<Schema> extends FieldConstraint<Schema> {
@@ -211,8 +211,5 @@ export declare function validateConstraint(options: {
211
211
  }) => string[];
212
212
  }): Submission;
213
213
  export declare function reportSubmission(form: HTMLFormElement, submission: Submission): void;
214
- /**
215
- * Check if the current focus is on a intent button.
216
- */
217
- export declare function isFocusedOnIntentButton(form: HTMLFormElement, intent: string): boolean;
214
+ export declare function getScope(intent: ReturnType<typeof parseIntent>): string | null;
218
215
  export {};
package/hooks.js CHANGED
@@ -144,7 +144,8 @@ function useForm() {
144
144
  if (!submission) {
145
145
  return {};
146
146
  }
147
- var scope = dom.getScope(submission.intent);
147
+ var intent = dom.parseIntent(submission.intent);
148
+ var scope = getScope(intent);
148
149
  return scope === null ? submission.error : {
149
150
  [scope]: submission.error[scope]
150
151
  };
@@ -230,15 +231,30 @@ function useForm() {
230
231
  form,
231
232
  formData
232
233
  })) !== null && _config$onValidate !== void 0 ? _config$onValidate : dom.parse(formData);
233
- var messages = Object.entries(submission.error).reduce((messages, _ref2) => {
234
- var [, message] = _ref2;
235
- return messages.concat(normalizeError(message));
236
- }, []);
237
- var shouldValidate = !config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate);
238
- var shouldFallbackToServer = messages.includes(VALIDATION_UNDEFINED);
239
- var hasClientValidation = typeof config.onValidate !== 'undefined';
240
- var isValid = messages.length === 0;
241
- if (hasClientValidation && (dom.isSubmitting(submission.intent) ? shouldValidate && !isValid : !shouldFallbackToServer)) {
234
+ var {
235
+ errors: _errors,
236
+ shouldServerValidate
237
+ } = Object.entries(submission.error).reduce((result, _ref2) => {
238
+ var [, error] = _ref2;
239
+ for (var message of normalizeError(error)) {
240
+ if (message === VALIDATION_UNDEFINED) {
241
+ result.shouldServerValidate = true;
242
+ } else if (message !== VALIDATION_SKIPPED) {
243
+ result.errors.push(message);
244
+ }
245
+ }
246
+ return result;
247
+ }, {
248
+ errors: [],
249
+ shouldServerValidate: false
250
+ });
251
+ if (
252
+ // has client validation
253
+ typeof config.onValidate !== 'undefined' &&
254
+ // not necessary to validate on the server
255
+ !shouldServerValidate && (
256
+ // client validation failed or non submit intent
257
+ !config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && _errors.length > 0 || dom.parseIntent(submission.intent) !== null)) {
242
258
  report(form, submission);
243
259
  event.preventDefault();
244
260
  } else {
@@ -341,27 +357,23 @@ function useFieldList(ref, config) {
341
357
  if (!form || event.target !== form) {
342
358
  return;
343
359
  }
344
- var command = dom.parseListCommand(event.detail);
345
- if ((command === null || command === void 0 ? void 0 : command.scope) !== configRef.current.name) {
346
- // Ensure the scope of the listener are limited to specific field name
360
+ var intent = dom.parseIntent(event.detail);
361
+ if ((intent === null || intent === void 0 ? void 0 : intent.type) !== 'list' || (intent === null || intent === void 0 ? void 0 : intent.payload.name) !== configRef.current.name) {
347
362
  return;
348
363
  }
349
364
  setEntries(entries => {
350
- switch (command.type) {
365
+ var list = [...entries];
366
+ switch (intent.payload.operation) {
351
367
  case 'append':
352
368
  case 'prepend':
353
369
  case 'replace':
354
- return dom.updateList([...(entries !== null && entries !== void 0 ? entries : [])], _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command), {}, {
355
- payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command.payload), {}, {
356
- defaultValue: ["".concat(Date.now()),
357
- // @ts-expect-error unknown type as it is sent through network
358
- command.payload.defaultValue]
359
- })
370
+ return dom.updateList(list, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, intent.payload), {}, {
371
+ defaultValue: [
372
+ // Generate a random key to avoid conflicts
373
+ crypto.getRandomValues(new Uint32Array(1))[0].toString(36), intent.payload.defaultValue]
360
374
  }));
361
375
  default:
362
- {
363
- return dom.updateList([...(entries !== null && entries !== void 0 ? entries : [])], command);
364
- }
376
+ return dom.updateList(list, intent.payload);
365
377
  }
366
378
  });
367
379
  setError(error => {
@@ -371,21 +383,17 @@ function useFieldList(ref, config) {
371
383
  errorList[key] = messages;
372
384
  }
373
385
  }
374
- switch (command.type) {
386
+ switch (intent.payload.operation) {
375
387
  case 'append':
376
388
  case 'prepend':
377
389
  case 'replace':
378
- errorList = dom.updateList(errorList, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command), {}, {
379
- payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command.payload), {}, {
380
- defaultValue: undefined
381
- })
390
+ errorList = dom.updateList(errorList, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, intent.payload), {}, {
391
+ defaultValue: undefined
382
392
  }));
383
393
  break;
384
394
  default:
385
- {
386
- errorList = dom.updateList(errorList, command);
387
- break;
388
- }
395
+ errorList = dom.updateList(errorList, intent.payload);
396
+ break;
389
397
  }
390
398
  return Object.assign({}, errorList);
391
399
  });
@@ -722,7 +730,8 @@ function reportSubmission(form, submission) {
722
730
  form.appendChild(button);
723
731
  }
724
732
  }
725
- var scope = dom.getScope(submission.intent);
733
+ var intent = dom.parseIntent(submission.intent);
734
+ var scope = getScope(intent);
726
735
  for (var element of dom.getFormControls(form)) {
727
736
  var _elementName = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
728
737
  var messages = normalizeError(submission.error[_elementName]);
@@ -737,27 +746,24 @@ function reportSubmission(form, submission) {
737
746
  element.dispatchEvent(invalidEvent);
738
747
  }
739
748
  }
740
- if (dom.isSubmitting(submission.intent) || isFocusedOnIntentButton(form, submission.intent)) {
741
- if (scope) {
742
- dom.focusFormControl(form, scope);
743
- } else {
744
- dom.focusFirstInvalidControl(form);
745
- }
749
+ if (!intent) {
750
+ dom.focusFirstInvalidControl(form);
746
751
  }
747
752
  }
748
-
749
- /**
750
- * Check if the current focus is on a intent button.
751
- */
752
- function isFocusedOnIntentButton(form, intent) {
753
- var element = document.activeElement;
754
- return dom.isFieldElement(element) && element.type === 'submit' && element.form === form && element.name === dom.INTENT && element.value === intent;
753
+ function getScope(intent) {
754
+ switch (intent === null || intent === void 0 ? void 0 : intent.type) {
755
+ case 'validate':
756
+ return intent.payload;
757
+ case 'list':
758
+ return intent.payload.name;
759
+ }
760
+ return null;
755
761
  }
756
762
 
757
763
  exports.FORM_ERROR_ELEMENT_NAME = FORM_ERROR_ELEMENT_NAME;
758
764
  exports.VALIDATION_SKIPPED = VALIDATION_SKIPPED;
759
765
  exports.VALIDATION_UNDEFINED = VALIDATION_UNDEFINED;
760
- exports.isFocusedOnIntentButton = isFocusedOnIntentButton;
766
+ exports.getScope = getScope;
761
767
  exports.reportSubmission = reportSubmission;
762
768
  exports.useFieldList = useFieldList;
763
769
  exports.useFieldset = useFieldset;
package/hooks.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
2
- import { getScope, getFormData, parse, isSubmitting, getFormAction, getFormEncType, getFormMethod, getPaths, getName, isFieldElement, getErrors, getFormControls, getFormElement, parseListCommand, updateList, getValidationMessage, focusFormControl, focusFirstInvalidControl, INTENT, isFocusableFormControl, requestIntent, validate } from '@conform-to/dom';
2
+ import { parseIntent, getFormData, parse, getFormAction, getFormEncType, getFormMethod, getPaths, getName, isFieldElement, getErrors, getFormControls, getFormElement, updateList, getValidationMessage, focusFirstInvalidControl, isFocusableFormControl, requestIntent, validate } from '@conform-to/dom';
3
3
  import { useState, useMemo, useEffect, useRef, useCallback, useLayoutEffect } from 'react';
4
4
 
5
5
  /**
@@ -140,7 +140,8 @@ function useForm() {
140
140
  if (!submission) {
141
141
  return {};
142
142
  }
143
- var scope = getScope(submission.intent);
143
+ var intent = parseIntent(submission.intent);
144
+ var scope = getScope(intent);
144
145
  return scope === null ? submission.error : {
145
146
  [scope]: submission.error[scope]
146
147
  };
@@ -226,15 +227,30 @@ function useForm() {
226
227
  form,
227
228
  formData
228
229
  })) !== null && _config$onValidate !== void 0 ? _config$onValidate : parse(formData);
229
- var messages = Object.entries(submission.error).reduce((messages, _ref2) => {
230
- var [, message] = _ref2;
231
- return messages.concat(normalizeError(message));
232
- }, []);
233
- var shouldValidate = !config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate);
234
- var shouldFallbackToServer = messages.includes(VALIDATION_UNDEFINED);
235
- var hasClientValidation = typeof config.onValidate !== 'undefined';
236
- var isValid = messages.length === 0;
237
- if (hasClientValidation && (isSubmitting(submission.intent) ? shouldValidate && !isValid : !shouldFallbackToServer)) {
230
+ var {
231
+ errors: _errors,
232
+ shouldServerValidate
233
+ } = Object.entries(submission.error).reduce((result, _ref2) => {
234
+ var [, error] = _ref2;
235
+ for (var message of normalizeError(error)) {
236
+ if (message === VALIDATION_UNDEFINED) {
237
+ result.shouldServerValidate = true;
238
+ } else if (message !== VALIDATION_SKIPPED) {
239
+ result.errors.push(message);
240
+ }
241
+ }
242
+ return result;
243
+ }, {
244
+ errors: [],
245
+ shouldServerValidate: false
246
+ });
247
+ if (
248
+ // has client validation
249
+ typeof config.onValidate !== 'undefined' &&
250
+ // not necessary to validate on the server
251
+ !shouldServerValidate && (
252
+ // client validation failed or non submit intent
253
+ !config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && _errors.length > 0 || parseIntent(submission.intent) !== null)) {
238
254
  report(form, submission);
239
255
  event.preventDefault();
240
256
  } else {
@@ -337,27 +353,23 @@ function useFieldList(ref, config) {
337
353
  if (!form || event.target !== form) {
338
354
  return;
339
355
  }
340
- var command = parseListCommand(event.detail);
341
- if ((command === null || command === void 0 ? void 0 : command.scope) !== configRef.current.name) {
342
- // Ensure the scope of the listener are limited to specific field name
356
+ var intent = parseIntent(event.detail);
357
+ if ((intent === null || intent === void 0 ? void 0 : intent.type) !== 'list' || (intent === null || intent === void 0 ? void 0 : intent.payload.name) !== configRef.current.name) {
343
358
  return;
344
359
  }
345
360
  setEntries(entries => {
346
- switch (command.type) {
361
+ var list = [...entries];
362
+ switch (intent.payload.operation) {
347
363
  case 'append':
348
364
  case 'prepend':
349
365
  case 'replace':
350
- return updateList([...(entries !== null && entries !== void 0 ? entries : [])], _objectSpread2(_objectSpread2({}, command), {}, {
351
- payload: _objectSpread2(_objectSpread2({}, command.payload), {}, {
352
- defaultValue: ["".concat(Date.now()),
353
- // @ts-expect-error unknown type as it is sent through network
354
- command.payload.defaultValue]
355
- })
366
+ return updateList(list, _objectSpread2(_objectSpread2({}, intent.payload), {}, {
367
+ defaultValue: [
368
+ // Generate a random key to avoid conflicts
369
+ crypto.getRandomValues(new Uint32Array(1))[0].toString(36), intent.payload.defaultValue]
356
370
  }));
357
371
  default:
358
- {
359
- return updateList([...(entries !== null && entries !== void 0 ? entries : [])], command);
360
- }
372
+ return updateList(list, intent.payload);
361
373
  }
362
374
  });
363
375
  setError(error => {
@@ -367,21 +379,17 @@ function useFieldList(ref, config) {
367
379
  errorList[key] = messages;
368
380
  }
369
381
  }
370
- switch (command.type) {
382
+ switch (intent.payload.operation) {
371
383
  case 'append':
372
384
  case 'prepend':
373
385
  case 'replace':
374
- errorList = updateList(errorList, _objectSpread2(_objectSpread2({}, command), {}, {
375
- payload: _objectSpread2(_objectSpread2({}, command.payload), {}, {
376
- defaultValue: undefined
377
- })
386
+ errorList = updateList(errorList, _objectSpread2(_objectSpread2({}, intent.payload), {}, {
387
+ defaultValue: undefined
378
388
  }));
379
389
  break;
380
390
  default:
381
- {
382
- errorList = updateList(errorList, command);
383
- break;
384
- }
391
+ errorList = updateList(errorList, intent.payload);
392
+ break;
385
393
  }
386
394
  return Object.assign({}, errorList);
387
395
  });
@@ -718,7 +726,8 @@ function reportSubmission(form, submission) {
718
726
  form.appendChild(button);
719
727
  }
720
728
  }
721
- var scope = getScope(submission.intent);
729
+ var intent = parseIntent(submission.intent);
730
+ var scope = getScope(intent);
722
731
  for (var element of getFormControls(form)) {
723
732
  var _elementName = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
724
733
  var messages = normalizeError(submission.error[_elementName]);
@@ -733,21 +742,18 @@ function reportSubmission(form, submission) {
733
742
  element.dispatchEvent(invalidEvent);
734
743
  }
735
744
  }
736
- if (isSubmitting(submission.intent) || isFocusedOnIntentButton(form, submission.intent)) {
737
- if (scope) {
738
- focusFormControl(form, scope);
739
- } else {
740
- focusFirstInvalidControl(form);
741
- }
745
+ if (!intent) {
746
+ focusFirstInvalidControl(form);
742
747
  }
743
748
  }
744
-
745
- /**
746
- * Check if the current focus is on a intent button.
747
- */
748
- function isFocusedOnIntentButton(form, intent) {
749
- var element = document.activeElement;
750
- return isFieldElement(element) && element.type === 'submit' && element.form === form && element.name === INTENT && element.value === intent;
749
+ function getScope(intent) {
750
+ switch (intent === null || intent === void 0 ? void 0 : intent.type) {
751
+ case 'validate':
752
+ return intent.payload;
753
+ case 'list':
754
+ return intent.payload.name;
755
+ }
756
+ return null;
751
757
  }
752
758
 
753
- export { FORM_ERROR_ELEMENT_NAME, VALIDATION_SKIPPED, VALIDATION_UNDEFINED, isFocusedOnIntentButton, reportSubmission, useFieldList, useFieldset, useForm, useInputEvent, validateConstraint };
759
+ export { FORM_ERROR_ELEMENT_NAME, VALIDATION_SKIPPED, VALIDATION_UNDEFINED, getScope, reportSubmission, useFieldList, useFieldset, useForm, useInputEvent, validateConstraint };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Conform view adapter for react",
4
4
  "homepage": "https://conform.guide",
5
5
  "license": "MIT",
6
- "version": "0.7.0-pre.1",
6
+ "version": "0.7.0-pre.2",
7
7
  "main": "index.js",
8
8
  "module": "index.mjs",
9
9
  "types": "index.d.ts",
@@ -30,7 +30,7 @@
30
30
  "url": "https://github.com/edmundhung/conform/issues"
31
31
  },
32
32
  "dependencies": {
33
- "@conform-to/dom": "0.7.0-pre.1"
33
+ "@conform-to/dom": "0.7.0-pre.2"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "react": ">=16.8"