react-f0rm 0.2.2 → 0.3.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.
Files changed (45) hide show
  1. package/README.md +527 -33
  2. package/dist/devtools/index.cjs.js +737 -0
  3. package/dist/devtools/index.cjs.js.map +1 -0
  4. package/dist/devtools/index.d.ts +33 -0
  5. package/dist/devtools/index.mjs +717 -0
  6. package/dist/devtools/index.mjs.map +1 -0
  7. package/dist/form-61297bc0.d.ts +578 -0
  8. package/dist/form-94c70b4b.mjs +378 -0
  9. package/dist/form-94c70b4b.mjs.map +1 -0
  10. package/dist/form-b9441d8c.cjs.js +387 -0
  11. package/dist/form-b9441d8c.cjs.js.map +1 -0
  12. package/dist/index.cjs.js +1257 -157
  13. package/dist/index.cjs.js.map +1 -1
  14. package/dist/index.d.ts +357 -53
  15. package/dist/index.mjs +1676 -0
  16. package/dist/index.mjs.map +1 -0
  17. package/dist/index.umd.js +1257 -157
  18. package/dist/index.umd.js.map +1 -1
  19. package/dist/index.umd.min.js +2 -2
  20. package/dist/index.umd.min.js.map +1 -1
  21. package/dist/resolvers/standard-schema.cjs.js +90 -0
  22. package/dist/resolvers/standard-schema.cjs.js.map +1 -0
  23. package/dist/resolvers/standard-schema.d.ts +66 -0
  24. package/dist/resolvers/standard-schema.mjs +86 -0
  25. package/dist/resolvers/standard-schema.mjs.map +1 -0
  26. package/dist/resolvers/yup.cjs.js +12 -2
  27. package/dist/resolvers/yup.cjs.js.map +1 -1
  28. package/dist/resolvers/yup.d.ts +2 -2
  29. package/dist/resolvers/yup.mjs +23 -0
  30. package/dist/resolvers/yup.mjs.map +1 -0
  31. package/dist/resolvers/zod.cjs.js +14 -1
  32. package/dist/resolvers/zod.cjs.js.map +1 -1
  33. package/dist/resolvers/zod.d.ts +2 -2
  34. package/dist/resolvers/zod.mjs +23 -0
  35. package/dist/resolvers/zod.mjs.map +1 -0
  36. package/dist/validate-148fe167.d.ts +22 -0
  37. package/package.json +34 -8
  38. package/dist/form-d06e6444.d.ts +0 -201
  39. package/dist/index.esm.js +0 -593
  40. package/dist/index.esm.js.map +0 -1
  41. package/dist/resolvers/yup.esm.js +0 -13
  42. package/dist/resolvers/yup.esm.js.map +0 -1
  43. package/dist/resolvers/zod.esm.js +0 -10
  44. package/dist/resolvers/zod.esm.js.map +0 -1
  45. package/dist/validate-0f17f86a.d.ts +0 -8
package/dist/index.cjs.js CHANGED
@@ -47,9 +47,56 @@ function on(ee, key, handler) {
47
47
  };
48
48
  }
49
49
 
50
+ const pathCache = /* @__PURE__ */ new Map();
50
51
  function normalizePath(path) {
51
52
  if (Array.isArray(path)) return path;
52
- return path.split(/\.|\[/).map((prop) => prop.endsWith("]") ? Number.parseInt(prop, 10) : prop);
53
+ const cached = pathCache.get(path);
54
+ if (cached) return cached;
55
+ const value = parsePath(path);
56
+ pathCache.set(path, value);
57
+ return value;
58
+ }
59
+ function parsePath(path) {
60
+ const result = [];
61
+ let identifier = "";
62
+ const flushIdentifier = () => {
63
+ result.push(identifier);
64
+ identifier = "";
65
+ };
66
+ for (let i = 0; i < path.length; i++) {
67
+ const char = path[i];
68
+ if (char === ".") {
69
+ if (identifier !== "") flushIdentifier();
70
+ } else if (char === "[") {
71
+ if (identifier !== "") flushIdentifier();
72
+ const quote = path[i + 1];
73
+ if (quote === '"' || quote === "'") {
74
+ const close = path.indexOf(quote, i + 2);
75
+ if (close === -1) {
76
+ throw new TypeError(`Unterminated quote in path: ${path}`);
77
+ }
78
+ if (path[close + 1] !== "]") {
79
+ throw new TypeError(
80
+ `Expected "]" after quoted segment in path: ${path}`
81
+ );
82
+ }
83
+ result.push(path.slice(i + 2, close));
84
+ i = close + 1;
85
+ } else {
86
+ const close = path.indexOf("]", i + 1);
87
+ if (close === -1) {
88
+ throw new TypeError(`Unterminated bracket in path: ${path}`);
89
+ }
90
+ const content = path.slice(i + 1, close);
91
+ result.push(/^-?\d+$/.test(content) ? Number(content) : content);
92
+ i = close;
93
+ }
94
+ } else {
95
+ identifier += char;
96
+ }
97
+ }
98
+ if (identifier !== "" || result.length === 0) flushIdentifier();
99
+ return result;
53
100
  }
54
101
  function get(values, path) {
55
102
  return path.reduce((current, p) => {
@@ -57,6 +104,24 @@ function get(values, path) {
57
104
  return current[p];
58
105
  }, values);
59
106
  }
107
+ function unset(values, path) {
108
+ if (!path.length || values == null) return values;
109
+ const [prop, ...props] = path;
110
+ if (props.length) {
111
+ const next = unset(values[prop], props);
112
+ return next === values[prop] ? values : set(values, path, next);
113
+ }
114
+ if (Array.isArray(values)) {
115
+ if (!(prop in values)) return values;
116
+ const arr = values.slice();
117
+ delete arr[prop];
118
+ return arr;
119
+ }
120
+ if (typeof values !== "object" || !(prop in values)) return values;
121
+ const copy = { ...values };
122
+ delete copy[prop];
123
+ return copy;
124
+ }
60
125
  function set(values, path, value) {
61
126
  if (!path.length) return value;
62
127
  const [prop, ...props] = path;
@@ -67,6 +132,35 @@ function set(values, path, value) {
67
132
  }
68
133
  return { ...values, [prop]: set(values && values[prop], props, value) };
69
134
  }
135
+ function setOwned(root, path, value, owned) {
136
+ if (!path.length) return value;
137
+ let container = root;
138
+ let parent = null;
139
+ let parentProp = "";
140
+ for (let i = 0; i < path.length; i++) {
141
+ const prop = path[i];
142
+ if (!owned.has(container)) {
143
+ let copy;
144
+ if (typeof prop === "number") {
145
+ copy = Array.isArray(container) ? container.slice() : [];
146
+ } else {
147
+ copy = { ...container };
148
+ }
149
+ owned.add(copy);
150
+ if (i === 0) root = copy;
151
+ else parent[parentProp] = copy;
152
+ container = copy;
153
+ }
154
+ if (i === path.length - 1) {
155
+ container[prop] = value;
156
+ } else {
157
+ parent = container;
158
+ parentProp = prop;
159
+ container = container[prop];
160
+ }
161
+ }
162
+ return root;
163
+ }
70
164
  function isPromise(value) {
71
165
  return value && typeof value.then === "function";
72
166
  }
@@ -80,7 +174,7 @@ function waitUntil(emitter, event, isResolve, isReject) {
80
174
  reject();
81
175
  return;
82
176
  }
83
- if (isResolve()) return;
177
+ if (!isResolve()) return;
84
178
  off();
85
179
  resolve();
86
180
  });
@@ -93,85 +187,148 @@ function create$1(name) {
93
187
  }
94
188
 
95
189
  const emit = emit$1;
190
+ const VALIDATION_OUTCOME = /* @__PURE__ */ Symbol("validation-outcome");
96
191
  function create(options) {
97
192
  const emitter = create$2();
98
193
  return {
99
194
  emitter,
100
- revalidateOnChange: true,
101
195
  ...options,
196
+ mode: options?.mode ?? "onSubmit",
197
+ reValidateMode: options?.reValidateMode ?? "onChange",
198
+ disabled: options?.disabled ?? false,
102
199
  initialValues: options?.initialValues ?? {},
103
200
  values: /* @__PURE__ */ new Map(),
201
+ deleted: /* @__PURE__ */ new Set(),
104
202
  errors: /* @__PURE__ */ new Map(),
105
203
  touched: /* @__PURE__ */ new Set(),
106
204
  validators: /* @__PURE__ */ new Map(),
107
205
  validating: /* @__PURE__ */ new Set(),
206
+ parsedValues: void 0,
108
207
  isSubmitting: false,
109
208
  submitCount: 0,
110
209
  isSubmitSuccessful: void 0
111
210
  };
112
211
  }
113
212
  function getValues(form) {
114
- return Array.from(form.values.keys()).reduce(
115
- (v, k) => set(v, JSON.parse(k), form.values.get(k)),
116
- form.initialValues
117
- );
213
+ const { initialValues, parsedValues, values, deleted } = form;
214
+ const owned = /* @__PURE__ */ new Set();
215
+ let merged = parsedValues ?? initialValues;
216
+ for (const [key, value] of values) {
217
+ merged = setOwned(merged, JSON.parse(key), value, owned);
218
+ }
219
+ for (const key of deleted) {
220
+ merged = unset(merged, JSON.parse(key));
221
+ }
222
+ return merged;
118
223
  }
119
224
  function getValue(form, name) {
120
225
  return getValueByPath(form, create$1(name));
121
226
  }
122
- function getValueByPath({ initialValues, values }, path) {
227
+ function getValueByPath({ initialValues, parsedValues, values, deleted }, path) {
123
228
  if (values.has(path.key)) return values.get(path.key);
124
- return get(initialValues, path.value);
229
+ if (deleted.has(path.key)) return void 0;
230
+ return get(parsedValues ?? initialValues, path.value);
125
231
  }
126
- function setValue(form, name, value) {
127
- setValueByPath(form, create$1(name), value);
232
+ function setValue(form, name, value, options) {
233
+ setValueByPath(form, create$1(name), value, options);
128
234
  }
129
- function setValueByPath({ emitter, values }, path, value) {
235
+ function setValueByPath(form, path, value, options) {
236
+ const { emitter, values, deleted } = form;
130
237
  values.set(path.key, value);
238
+ reviveBranch(deleted, path);
239
+ bumpDirtyVersion(form);
240
+ if (options?.shouldTouch) setTouchedByPath(form, path);
241
+ if (options?.shouldValidate) form.validators.get(path.key)?.();
131
242
  emit(emitter, "change", path);
132
243
  }
133
244
  function getError(form, name) {
134
245
  return getErrorByPath(form, create$1(name));
135
246
  }
136
247
  function getErrorByPath({ errors }, path) {
137
- return errors.get(path.key);
248
+ return errors.get(path.key)?.[0];
249
+ }
250
+ const NO_ERRORS = [];
251
+ function getFieldErrors(form, name) {
252
+ return getFieldErrorsByPath(form, create$1(name));
253
+ }
254
+ function getFieldErrorsByPath({ errors }, path) {
255
+ return errors.get(path.key) ?? NO_ERRORS;
138
256
  }
139
257
  function getErrors({ errors }) {
140
- return Array.from(errors.values());
258
+ const entries = [];
259
+ for (const [key, list] of errors) {
260
+ const path = JSON.parse(key).join(".");
261
+ for (const { type, message } of list) entries.push({ path, type, message });
262
+ }
263
+ return entries;
141
264
  }
142
265
  function getFirstError({ errors }) {
143
- return errors.values().next().value;
266
+ return errors.values().next().value?.[0]?.message;
144
267
  }
145
- function unsetValidatingByPath({ emitter, validating }, { key }) {
146
- validating.delete(key);
147
- emit(emitter, "validating");
268
+ function getFieldState(form, name) {
269
+ const path = create$1(name);
270
+ const { initialValues, values, touched, validating } = form;
271
+ const live = values.get(path.key);
272
+ return {
273
+ value: getValueByPath(form, path),
274
+ error: getErrorByPath(form, path),
275
+ errors: getFieldErrorsByPath(form, path),
276
+ isDirty: values.has(path.key) && get(initialValues, path.value) !== live,
277
+ isTouched: touched.has(path.key),
278
+ isValidating: validating.has(path.key)
279
+ };
148
280
  }
149
- function setValidatingByPath({ emitter, validating }, { key }) {
150
- validating.add(key);
151
- emit(emitter, "validating");
281
+ function unsetValidatingByPath({ emitter, validating }, path) {
282
+ validating.delete(path.key);
283
+ emit(emitter, "validating", path);
284
+ }
285
+ function setValidatingByPath({ emitter, validating }, path) {
286
+ validating.add(path.key);
287
+ emit(emitter, "validating", path);
152
288
  }
153
289
  function setError(form, name, error) {
154
290
  setErrorByPath(form, create$1(name), error);
155
291
  }
156
292
  function setErrorByPath({ emitter, errors }, path, error) {
157
- if (error) {
158
- errors.set(path.key, error);
159
- } else {
160
- errors.delete(path.key);
293
+ const list = normalizeErrors(error);
294
+ if (list) errors.set(path.key, list);
295
+ else errors.delete(path.key);
296
+ emit(emitter, "errors", path);
297
+ }
298
+ function normalizeErrors(error) {
299
+ if (typeof error === "string") {
300
+ return error ? [{ type: "custom", message: error }] : void 0;
161
301
  }
162
- emit(emitter, "errors");
163
- }
164
- function clearErrors({ emitter, errors }) {
165
- errors.clear();
166
- emit(emitter, "errors");
302
+ if (isFieldError(error)) return [error];
303
+ if (!error) return void 0;
304
+ const list = [];
305
+ error.forEach((item) => {
306
+ if (typeof item === "string" && item) {
307
+ list.push({ type: "custom", message: item });
308
+ } else if (isFieldError(item)) {
309
+ list.push(item);
310
+ }
311
+ });
312
+ return list.length ? list : void 0;
313
+ }
314
+ function clearErrors(form, name) {
315
+ const { emitter, errors } = form;
316
+ if (name === void 0) {
317
+ errors.clear();
318
+ emit(emitter, "errors");
319
+ return;
320
+ }
321
+ const paths = typeof name === "string" || isSegmentsPath(name) ? [create$1(name)] : name.map((one) => create$1(one));
322
+ for (const { key } of paths) errors.delete(key);
323
+ for (const path of paths) emit(emitter, "errors", path);
167
324
  }
168
325
  function setTouched(form, name) {
169
326
  setTouchedByPath(form, create$1(name));
170
327
  }
171
- function setTouchedByPath({ emitter, touched }, { key }) {
172
- if (touched.has(key)) return;
173
- touched.add(key);
174
- emit(emitter, "touched");
328
+ function setTouchedByPath({ emitter, touched }, path) {
329
+ if (touched.has(path.key)) return;
330
+ touched.add(path.key);
331
+ emit(emitter, "touched", path);
175
332
  }
176
333
  function hasTouched(form, name) {
177
334
  return hasTouchedByPath(form, create$1(name));
@@ -179,12 +336,53 @@ function hasTouched(form, name) {
179
336
  function hasTouchedByPath({ touched }, path) {
180
337
  return touched.has(path.key);
181
338
  }
182
- function isDirty({ initialValues, values }) {
339
+ function isDirty(form) {
340
+ let dirty = false;
341
+ forEachDirtyField(form, () => {
342
+ dirty = true;
343
+ });
344
+ return dirty;
345
+ }
346
+ function forEachDirtyField({ initialValues, values }, fn) {
183
347
  for (const [key, value] of values) {
184
348
  const path = JSON.parse(key);
185
- if (get(initialValues, path) !== value) return true;
349
+ if (get(initialValues, path) !== value) fn(path.join("."));
186
350
  }
187
- return false;
351
+ }
352
+ const dirtyFieldsCaches = /* @__PURE__ */ new WeakMap();
353
+ function bumpDirtyVersion(form) {
354
+ const cache = dirtyFieldsCaches.get(form);
355
+ if (cache) cache.version++;
356
+ }
357
+ function computeDirtyFields(form) {
358
+ const dirtyFields = {};
359
+ forEachDirtyField(form, (key) => {
360
+ dirtyFields[key] = true;
361
+ });
362
+ return dirtyFields;
363
+ }
364
+ function sameDirtyKeys(a, b) {
365
+ const aKeys = Object.keys(a);
366
+ if (aKeys.length !== Object.keys(b).length) return false;
367
+ return aKeys.every((key) => b[key] === true);
368
+ }
369
+ function getDirtyFields(form) {
370
+ let cache = dirtyFieldsCaches.get(form);
371
+ if (!cache) {
372
+ cache = { version: 0, result: computeDirtyFields(form) };
373
+ dirtyFieldsCaches.set(form, cache);
374
+ } else if (cache.version > 0) {
375
+ const result = computeDirtyFields(form);
376
+ if (!sameDirtyKeys(cache.result, result)) cache.result = result;
377
+ cache.version = 0;
378
+ }
379
+ return cache.result;
380
+ }
381
+ function getTouchedFields({ touched }) {
382
+ return Array.from(
383
+ touched,
384
+ (key) => JSON.parse(key).join(".")
385
+ );
188
386
  }
189
387
  function isTouched({ touched }) {
190
388
  return touched.size > 0;
@@ -192,38 +390,154 @@ function isTouched({ touched }) {
192
390
  function removeField(form, name) {
193
391
  removeFieldByPath(form, create$1(name));
194
392
  }
195
- function removeFieldByPath(form, { key }) {
196
- const { emitter, values, touched, errors, validating } = form;
393
+ function removeFieldByPath(form, { key, value: segments }) {
394
+ const { emitter, values, touched, errors, validating, deleted } = form;
197
395
  values.delete(key);
198
396
  touched.delete(key);
199
397
  errors.delete(key);
200
398
  validating.delete(key);
399
+ if (!hasLiveBranch(values, segments)) deleted.add(key);
400
+ bumpDirtyVersion(form);
201
401
  emit(emitter, "change");
202
402
  emit(emitter, "touched");
203
403
  emit(emitter, "errors");
204
404
  emit(emitter, "validating");
205
405
  }
406
+ function hasLiveBranch(values, segments) {
407
+ for (let i = 1; i < segments.length; i++) {
408
+ if (values.has(JSON.stringify(segments.slice(0, i)))) return true;
409
+ }
410
+ const stem = `${JSON.stringify(segments).slice(0, -1)},`;
411
+ for (const key of values.keys()) {
412
+ if (key.startsWith(stem)) return true;
413
+ }
414
+ return false;
415
+ }
416
+ function reviveBranch(deleted, { key }) {
417
+ if (!deleted.size) return;
418
+ for (const tombstone of deleted) {
419
+ if (tombstone === key || tombstone.startsWith(`${key.slice(0, -1)},`) || key.startsWith(`${tombstone.slice(0, -1)},`)) {
420
+ deleted.delete(tombstone);
421
+ }
422
+ }
423
+ }
206
424
  function setInitialValues(form, initialValues) {
207
425
  if (form.initialValues === initialValues) return;
208
426
  form.initialValues = initialValues;
427
+ form.parsedValues = void 0;
209
428
  form.values.clear();
429
+ form.deleted.clear();
430
+ bumpDirtyVersion(form);
210
431
  emit(form.emitter, "change");
211
432
  }
212
- function reset(form, initialValues) {
433
+ function reset(form, initialValues, options) {
434
+ const dirtyValues = options?.keepDirtyValues ? Object.keys(getDirtyFields(form)).map((key) => ({
435
+ key,
436
+ value: getValue(form, key)
437
+ })) : [];
213
438
  form.initialValues = initialValues;
214
- clearErrors(form);
215
- const { emitter, touched, values } = form;
439
+ form.parsedValues = void 0;
440
+ if (!options?.keepErrors) clearErrors(form);
441
+ const { emitter, touched, values, deleted, validating } = form;
216
442
  values.clear();
217
- touched.clear();
443
+ deleted.clear();
444
+ if (!options?.keepTouched) touched.clear();
445
+ validating.clear();
446
+ if (!options?.keepIsSubmitting) form.isSubmitting = false;
447
+ if (!options?.keepSubmitCount) form.submitCount = 0;
448
+ if (!options?.keepIsSubmitted) form.isSubmitSuccessful = void 0;
449
+ bumpDirtyVersion(form);
450
+ for (const { key, value } of dirtyValues) {
451
+ setValueByPath(form, create$1(key), value);
452
+ }
218
453
  emit(emitter, "change");
219
454
  emit(emitter, "touched");
455
+ emit(emitter, "validating");
456
+ emit(emitter, "submitting");
457
+ emit(emitter, "submitCount");
458
+ emit(emitter, "submitSuccessful");
220
459
  emit(emitter, "reset");
221
460
  }
461
+ function resetField(form, name, options) {
462
+ const path = create$1(name);
463
+ const { emitter, values, touched, errors, deleted } = form;
464
+ values.delete(path.key);
465
+ if (form.parsedValues !== void 0) {
466
+ form.parsedValues = unset(form.parsedValues, path.value);
467
+ const initial = get(form.initialValues, path.value);
468
+ if (initial !== void 0) values.set(path.key, initial);
469
+ }
470
+ if (options && "value" in options) {
471
+ values.set(path.key, options.value);
472
+ }
473
+ reviveBranch(deleted, path);
474
+ emit(emitter, "change");
475
+ if (!options?.keepTouched && touched.delete(path.key)) {
476
+ emit(emitter, "touched", path);
477
+ }
478
+ if (!options?.keepErrors && errors.delete(path.key)) {
479
+ emit(emitter, "errors", path);
480
+ }
481
+ bumpDirtyVersion(form);
482
+ }
222
483
  function hasErrors({ errors }) {
223
484
  return errors.size > 0;
224
485
  }
225
- function trigger(form) {
226
- form.validators.forEach((validator) => validator());
486
+ async function trigger(form, name) {
487
+ const settle = () => waitUntil(
488
+ form.emitter,
489
+ "validating",
490
+ () => !form.validating.size,
491
+ () => false
492
+ );
493
+ if (name === void 0) {
494
+ form.validators.forEach((validator) => validator());
495
+ await settle();
496
+ if (form.validate) {
497
+ const result = await form.validate(getValues(form));
498
+ applyValidateResult(form, result);
499
+ }
500
+ return !hasErrors(form);
501
+ }
502
+ const keys = typeof name === "string" || isSegmentsPath(name) ? [create$1(name).key] : name.map((one) => create$1(one).key);
503
+ keys.forEach((key) => form.validators.get(key)?.());
504
+ await settle();
505
+ return keys.every((key) => !form.errors.has(key));
506
+ }
507
+ function isSegmentsPath(name) {
508
+ return name.some((part) => typeof part === "number");
509
+ }
510
+ function isFieldError(value) {
511
+ return !!value && typeof value === "object" && typeof value.type === "string" && typeof value.message === "string";
512
+ }
513
+ function setFormErrors(form, result, segments = []) {
514
+ Object.entries(result).forEach(([key, value]) => {
515
+ const path = [...segments, ...normalizePath(key)];
516
+ if (typeof value === "string") {
517
+ if (value) setError(form, path, value);
518
+ } else if (Array.isArray(value)) {
519
+ setError(form, path, value);
520
+ } else if (isFieldError(value)) {
521
+ setError(form, path, value);
522
+ } else if (value && typeof value === "object") {
523
+ setFormErrors(form, value, path);
524
+ }
525
+ });
526
+ }
527
+ function setParsedValues(form, values) {
528
+ if (values === void 0 || values === form.parsedValues) return;
529
+ form.parsedValues = values;
530
+ emit(form.emitter, "change");
531
+ }
532
+ function applyValidateResult(form, result) {
533
+ if (!result) return;
534
+ if (typeof result === "object" && VALIDATION_OUTCOME in result) {
535
+ const outcome = result;
536
+ if (outcome.errors) setFormErrors(form, outcome.errors);
537
+ setParsedValues(form, outcome.values);
538
+ return;
539
+ }
540
+ setFormErrors(form, result);
227
541
  }
228
542
  async function ensureValidate(form) {
229
543
  form.validators.forEach((validator) => validator());
@@ -237,13 +551,8 @@ async function ensureValidate(form) {
237
551
  });
238
552
  if (form.validate) {
239
553
  const result = await form.validate(getValues(form));
240
- const entries = result ? Object.entries(result) : [];
241
- if (entries.length) {
242
- entries.forEach(([field, error]) => {
243
- setError(form, field, error);
244
- });
245
- throw new Error(getFirstError(form));
246
- }
554
+ applyValidateResult(form, result);
555
+ if (hasErrors(form)) throw new Error(getFirstError(form));
247
556
  }
248
557
  }
249
558
  async function validate(form) {
@@ -261,68 +570,510 @@ function setSubmitSuccessful(form, value) {
261
570
  form.isSubmitSuccessful = value;
262
571
  emit(form.emitter, "submitSuccessful");
263
572
  }
573
+ function setDisabled(form, value) {
574
+ form.disabled = value;
575
+ emit(form.emitter, "disabled");
576
+ }
577
+ function nameToPath(name) {
578
+ if (name.startsWith("[")) {
579
+ try {
580
+ const segments = JSON.parse(name);
581
+ if (Array.isArray(segments)) return segments.join(".");
582
+ } catch {
583
+ }
584
+ }
585
+ return name;
586
+ }
587
+ function getNativeErrors(formEl) {
588
+ const errors = [];
589
+ const { elements } = formEl;
590
+ for (let i = 0; i < elements.length; i++) {
591
+ const el = elements[i];
592
+ if (el.name && typeof el.checkValidity === "function" && !el.checkValidity()) {
593
+ errors.push({
594
+ path: nameToPath(el.name),
595
+ type: "native",
596
+ message: el.validationMessage
597
+ });
598
+ }
599
+ }
600
+ return errors;
601
+ }
602
+ function handleSubmit(form, options) {
603
+ const {
604
+ onSubmit,
605
+ onValidSubmit,
606
+ onInvalidSubmit,
607
+ shouldFocusError = true
608
+ } = options ?? {};
609
+ return async (e) => {
610
+ if (e && typeof e.preventDefault === "function") {
611
+ e.preventDefault();
612
+ }
613
+ const formEl = e?.currentTarget;
614
+ setIsSubmitting(form, true);
615
+ incrementSubmitCount(form);
616
+ const values = getValues(form);
617
+ if (formEl && typeof formEl.checkValidity === "function" && formEl.checkValidity() === false) {
618
+ formEl.reportValidity();
619
+ if (shouldFocusError && typeof formEl.querySelector === "function") {
620
+ const invalid = formEl.querySelector(":invalid");
621
+ if (invalid && typeof invalid.focus === "function") invalid.focus();
622
+ }
623
+ setIsSubmitting(form, false);
624
+ setSubmitSuccessful(form, false);
625
+ if (onInvalidSubmit) onInvalidSubmit(getNativeErrors(formEl), values);
626
+ return;
627
+ }
628
+ const error = await validate(form);
629
+ if (error) {
630
+ setIsSubmitting(form, false);
631
+ setSubmitSuccessful(form, false);
632
+ if (shouldFocusError) {
633
+ const firstKey = form.errors.keys().next().value;
634
+ if (firstKey !== void 0) emit(form.emitter, "focusError", firstKey);
635
+ }
636
+ if (onInvalidSubmit) onInvalidSubmit(getErrors(form), values);
637
+ return;
638
+ }
639
+ try {
640
+ const submitted = getValues(form);
641
+ if (onSubmit) await onSubmit(submitted, e);
642
+ if (onValidSubmit) await onValidSubmit(submitted, e);
643
+ setSubmitSuccessful(form, true);
644
+ } catch {
645
+ setSubmitSuccessful(form, false);
646
+ } finally {
647
+ setIsSubmitting(form, false);
648
+ }
649
+ };
650
+ }
651
+ function setFocus(form, name, options) {
652
+ const { key } = create$1(name);
653
+ if (options) emit(form.emitter, "focusError", key, options);
654
+ else emit(form.emitter, "focusError", key);
655
+ }
264
656
 
265
- const FormContext = React.createContext(null);
266
- const FormProvider = FormContext.Provider;
267
- function useFormContext() {
268
- const form = React.useContext(FormContext);
269
- if (!form) throw new Error("no form provided");
270
- return form;
657
+ function defaultMessage(type, bound) {
658
+ switch (type) {
659
+ case "required":
660
+ return "This field is required";
661
+ case "min":
662
+ return `Must be at least ${bound}`;
663
+ case "max":
664
+ return `Must be at most ${bound}`;
665
+ case "minLength":
666
+ return `Must be at least ${bound} characters`;
667
+ case "maxLength":
668
+ return `Must be at most ${bound} characters`;
669
+ case "pattern":
670
+ return "Invalid format";
671
+ default:
672
+ return "Invalid value";
673
+ }
271
674
  }
272
- const CheckboxGroupContext = React.createContext(null);
273
- const CheckboxGroupProvider = CheckboxGroupContext.Provider;
274
- function useCheckboxGroupContext() {
275
- const group = React.useContext(CheckboxGroupContext);
276
- if (!group) throw new Error("no group provided");
277
- return group;
675
+ function rulesToValidator(rules) {
676
+ return (value) => {
677
+ if (rules.required) {
678
+ if (value === "" || value === void 0 || value === null) {
679
+ return [
680
+ {
681
+ type: "required",
682
+ message: typeof rules.required === "string" ? rules.required : defaultMessage("required")
683
+ }
684
+ ];
685
+ }
686
+ }
687
+ const errors = [];
688
+ const message = (type, bound) => rules.messages?.[type] ?? defaultMessage(type, bound);
689
+ if (rules.min !== void 0) {
690
+ const n = Number(value);
691
+ if (!Number.isNaN(n) && n < rules.min) {
692
+ errors.push({ type: "min", message: message("min", rules.min) });
693
+ }
694
+ }
695
+ if (rules.max !== void 0) {
696
+ const n = Number(value);
697
+ if (!Number.isNaN(n) && n > rules.max) {
698
+ errors.push({ type: "max", message: message("max", rules.max) });
699
+ }
700
+ }
701
+ if (rules.minLength !== void 0 && typeof value === "string" && value.length < rules.minLength) {
702
+ errors.push({
703
+ type: "minLength",
704
+ message: message("minLength", rules.minLength)
705
+ });
706
+ }
707
+ if (rules.maxLength !== void 0 && typeof value === "string" && value.length > rules.maxLength) {
708
+ errors.push({
709
+ type: "maxLength",
710
+ message: message("maxLength", rules.maxLength)
711
+ });
712
+ }
713
+ if (rules.pattern && !rules.pattern.value.test(value)) {
714
+ errors.push({
715
+ type: "pattern",
716
+ // pattern.message is type-required but JS consumers may omit it.
717
+ message: rules.messages?.pattern ?? rules.pattern.message ?? defaultMessage("pattern")
718
+ });
719
+ }
720
+ return errors.length ? errors : void 0;
721
+ };
722
+ }
723
+
724
+ var shim = {exports: {}};
725
+
726
+ var useSyncExternalStoreShim_production = {};
727
+
728
+ /**
729
+ * @license React
730
+ * use-sync-external-store-shim.production.js
731
+ *
732
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
733
+ *
734
+ * This source code is licensed under the MIT license found in the
735
+ * LICENSE file in the root directory of this source tree.
736
+ */
737
+
738
+ var hasRequiredUseSyncExternalStoreShim_production;
739
+
740
+ function requireUseSyncExternalStoreShim_production () {
741
+ if (hasRequiredUseSyncExternalStoreShim_production) return useSyncExternalStoreShim_production;
742
+ hasRequiredUseSyncExternalStoreShim_production = 1;
743
+ var React$1 = React;
744
+ function is(x, y) {
745
+ return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y);
746
+ }
747
+ var objectIs = "function" === typeof Object.is ? Object.is : is,
748
+ useState = React$1.useState,
749
+ useEffect = React$1.useEffect,
750
+ useLayoutEffect = React$1.useLayoutEffect,
751
+ useDebugValue = React$1.useDebugValue;
752
+ function useSyncExternalStore$2(subscribe, getSnapshot) {
753
+ var value = getSnapshot(),
754
+ _useState = useState({ inst: { value: value, getSnapshot: getSnapshot } }),
755
+ inst = _useState[0].inst,
756
+ forceUpdate = _useState[1];
757
+ useLayoutEffect(
758
+ function () {
759
+ inst.value = value;
760
+ inst.getSnapshot = getSnapshot;
761
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
762
+ },
763
+ [subscribe, value, getSnapshot]
764
+ );
765
+ useEffect(
766
+ function () {
767
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
768
+ return subscribe(function () {
769
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
770
+ });
771
+ },
772
+ [subscribe]
773
+ );
774
+ useDebugValue(value);
775
+ return value;
776
+ }
777
+ function checkIfSnapshotChanged(inst) {
778
+ var latestGetSnapshot = inst.getSnapshot;
779
+ inst = inst.value;
780
+ try {
781
+ var nextValue = latestGetSnapshot();
782
+ return !objectIs(inst, nextValue);
783
+ } catch (error) {
784
+ return !0;
785
+ }
786
+ }
787
+ function useSyncExternalStore$1(subscribe, getSnapshot) {
788
+ return getSnapshot();
789
+ }
790
+ var shim =
791
+ "undefined" === typeof window ||
792
+ "undefined" === typeof window.document ||
793
+ "undefined" === typeof window.document.createElement
794
+ ? useSyncExternalStore$1
795
+ : useSyncExternalStore$2;
796
+ useSyncExternalStoreShim_production.useSyncExternalStore =
797
+ void 0 !== React$1.useSyncExternalStore ? React$1.useSyncExternalStore : shim;
798
+ return useSyncExternalStoreShim_production;
799
+ }
800
+
801
+ var useSyncExternalStoreShim_development = {};
802
+
803
+ /**
804
+ * @license React
805
+ * use-sync-external-store-shim.development.js
806
+ *
807
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
808
+ *
809
+ * This source code is licensed under the MIT license found in the
810
+ * LICENSE file in the root directory of this source tree.
811
+ */
812
+
813
+ var hasRequiredUseSyncExternalStoreShim_development;
814
+
815
+ function requireUseSyncExternalStoreShim_development () {
816
+ if (hasRequiredUseSyncExternalStoreShim_development) return useSyncExternalStoreShim_development;
817
+ hasRequiredUseSyncExternalStoreShim_development = 1;
818
+ "production" !== process.env.NODE_ENV &&
819
+ (function () {
820
+ function is(x, y) {
821
+ return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y);
822
+ }
823
+ function useSyncExternalStore$2(subscribe, getSnapshot) {
824
+ didWarnOld18Alpha ||
825
+ void 0 === React$1.startTransition ||
826
+ ((didWarnOld18Alpha = !0),
827
+ console.error(
828
+ "You are using an outdated, pre-release alpha of React 18 that does not support useSyncExternalStore. The use-sync-external-store shim will not work correctly. Upgrade to a newer pre-release."
829
+ ));
830
+ var value = getSnapshot();
831
+ if (!didWarnUncachedGetSnapshot) {
832
+ var cachedValue = getSnapshot();
833
+ objectIs(value, cachedValue) ||
834
+ (console.error(
835
+ "The result of getSnapshot should be cached to avoid an infinite loop"
836
+ ),
837
+ (didWarnUncachedGetSnapshot = !0));
838
+ }
839
+ cachedValue = useState({
840
+ inst: { value: value, getSnapshot: getSnapshot }
841
+ });
842
+ var inst = cachedValue[0].inst,
843
+ forceUpdate = cachedValue[1];
844
+ useLayoutEffect(
845
+ function () {
846
+ inst.value = value;
847
+ inst.getSnapshot = getSnapshot;
848
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
849
+ },
850
+ [subscribe, value, getSnapshot]
851
+ );
852
+ useEffect(
853
+ function () {
854
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
855
+ return subscribe(function () {
856
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
857
+ });
858
+ },
859
+ [subscribe]
860
+ );
861
+ useDebugValue(value);
862
+ return value;
863
+ }
864
+ function checkIfSnapshotChanged(inst) {
865
+ var latestGetSnapshot = inst.getSnapshot;
866
+ inst = inst.value;
867
+ try {
868
+ var nextValue = latestGetSnapshot();
869
+ return !objectIs(inst, nextValue);
870
+ } catch (error) {
871
+ return !0;
872
+ }
873
+ }
874
+ function useSyncExternalStore$1(subscribe, getSnapshot) {
875
+ return getSnapshot();
876
+ }
877
+ "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
878
+ "function" ===
879
+ typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart &&
880
+ __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
881
+ var React$1 = React,
882
+ objectIs = "function" === typeof Object.is ? Object.is : is,
883
+ useState = React$1.useState,
884
+ useEffect = React$1.useEffect,
885
+ useLayoutEffect = React$1.useLayoutEffect,
886
+ useDebugValue = React$1.useDebugValue,
887
+ didWarnOld18Alpha = !1,
888
+ didWarnUncachedGetSnapshot = !1,
889
+ shim =
890
+ "undefined" === typeof window ||
891
+ "undefined" === typeof window.document ||
892
+ "undefined" === typeof window.document.createElement
893
+ ? useSyncExternalStore$1
894
+ : useSyncExternalStore$2;
895
+ useSyncExternalStoreShim_development.useSyncExternalStore =
896
+ void 0 !== React$1.useSyncExternalStore ? React$1.useSyncExternalStore : shim;
897
+ "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
898
+ "function" ===
899
+ typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
900
+ __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
901
+ })();
902
+ return useSyncExternalStoreShim_development;
903
+ }
904
+
905
+ if (process.env.NODE_ENV === 'production') {
906
+ shim.exports = requireUseSyncExternalStoreShim_production();
907
+ } else {
908
+ shim.exports = requireUseSyncExternalStoreShim_development();
909
+ }
910
+
911
+ var shimExports = shim.exports;
912
+
913
+ function isDescendant(key, ancestorKey) {
914
+ return key.startsWith(`${ancestorKey.slice(0, -1)},`);
915
+ }
916
+ function onPathEvent(emitter, event, path, scope, cb) {
917
+ const { key } = path;
918
+ return on(emitter, event, (changed) => {
919
+ if (changed === void 0 || changed.key === key || isDescendant(key, changed.key) || scope === "branch" && isDescendant(changed.key, key)) {
920
+ cb();
921
+ }
922
+ });
923
+ }
924
+ function onKeyEvent(emitter, event, key, cb) {
925
+ return on(emitter, event, (changed) => {
926
+ if (changed === void 0 || changed.key === key) cb();
927
+ });
928
+ }
929
+ function isNameList(name) {
930
+ return Array.isArray(name) && name.every((part) => typeof part !== "number");
931
+ }
932
+ function subscribe(form, options) {
933
+ const { name, event = "change", scope = "branch", callback } = options;
934
+ if (name === void 0) return on(form.emitter, event, callback);
935
+ const names = isNameList(name) ? name : [name];
936
+ const unsubscribes = names.map((one) => {
937
+ const path = create$1(one);
938
+ return event === "errors" || event === "touched" ? onKeyEvent(form.emitter, event, path.key, callback) : onPathEvent(form.emitter, event, path, scope, callback);
939
+ });
940
+ return unsubscribes.length === 1 ? unsubscribes[0] : () => unsubscribes.forEach((unsubscribe) => unsubscribe());
278
941
  }
279
942
 
280
943
  function useForm(options) {
281
- const ref = React.useRef(null);
282
- const form = ref.current = ref.current || create(options);
944
+ const [form] = React.useState(() => {
945
+ const created = create(options);
946
+ if (options && options.values !== void 0) {
947
+ setInitialValues(created, options.values);
948
+ }
949
+ return created;
950
+ });
283
951
  const initialValues = options && options.initialValues;
952
+ const values = options && options.values;
953
+ const seededRef = React.useRef(null);
954
+ if (seededRef.current === null)
955
+ seededRef.current = { done: false, source: void 0 };
284
956
  React.useEffect(() => {
957
+ const seeded = seededRef.current;
958
+ if (seeded.done && (seeded.source === initialValues || isEqual(seeded.source, initialValues))) {
959
+ return;
960
+ }
961
+ seeded.done = true;
962
+ seeded.source = initialValues;
285
963
  setInitialValues(form, initialValues);
286
- }, [initialValues]);
964
+ }, [form, initialValues]);
965
+ const controlledRef = React.useRef(null);
966
+ if (controlledRef.current === null) {
967
+ controlledRef.current = { done: false, source: void 0 };
968
+ }
969
+ React.useEffect(() => {
970
+ if (values === void 0) return;
971
+ const seeded = controlledRef.current;
972
+ if (seeded.done && (seeded.source === values || isEqual(seeded.source, values))) {
973
+ return;
974
+ }
975
+ seeded.done = true;
976
+ seeded.source = values;
977
+ setInitialValues(form, values);
978
+ }, [form, values]);
287
979
  return form;
288
980
  }
981
+ function useWatchCore(subscribeFactory, getter) {
982
+ const cacheRef = React.useRef(null);
983
+ if (cacheRef.current === null) cacheRef.current = { hasValue: false };
984
+ const cache = cacheRef.current;
985
+ const getterRef = React.useRef(getter);
986
+ getterRef.current = getter;
987
+ const getSnapshot = React.useCallback(() => {
988
+ if (!cache.hasValue) {
989
+ cache.value = getterRef.current();
990
+ cache.hasValue = true;
991
+ }
992
+ return cache.value;
993
+ }, [cache]);
994
+ const subscribe = React.useCallback(
995
+ (notify) => {
996
+ cache.hasValue = false;
997
+ const invalidate = () => {
998
+ cache.hasValue = false;
999
+ notify();
1000
+ };
1001
+ return subscribeFactory(invalidate);
1002
+ },
1003
+ [subscribeFactory, cache]
1004
+ );
1005
+ return shimExports.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1006
+ }
289
1007
  function useWatch(emitter, event, getter) {
290
- const [value, syncValue] = React.useReducer(getter, void 0, getter);
291
- React.useEffect(() => on(emitter, event, syncValue), [emitter, event]);
292
- return value;
1008
+ const subscribeFactory = React.useCallback(
1009
+ (invalidate) => on(emitter, event, invalidate),
1010
+ [emitter, event]
1011
+ );
1012
+ return useWatchCore(subscribeFactory, getter);
293
1013
  }
294
1014
  function useValue(form, name) {
295
1015
  return useValueByPath(form, create$1(name));
296
1016
  }
297
1017
  function useValueByPath(form, path) {
298
- return useWatch(
299
- form.emitter,
300
- "change",
301
- getValueByPath.bind(null, form, path)
1018
+ const { emitter } = form;
1019
+ const { key } = path;
1020
+ const subscribeFactory = React.useCallback(
1021
+ (invalidate) => onPathEvent(emitter, "change", path, "leaf", invalidate),
1022
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- deps are `key` on purpose: useValue creates a fresh Path per render, so the object must stay out of the deps while the key string pins the subscription
1023
+ [emitter, key]
302
1024
  );
1025
+ return useWatchCore(subscribeFactory, getValueByPath.bind(null, form, path));
303
1026
  }
304
1027
  function useTouched(form, name) {
305
1028
  return useTouchedByPath(form, create$1(name));
306
1029
  }
307
1030
  function useTouchedByPath(form, path) {
308
- return useWatch(
309
- form.emitter,
310
- "touched",
1031
+ const { emitter } = form;
1032
+ const { key } = path;
1033
+ const subscribeFactory = React.useCallback(
1034
+ (invalidate) => onKeyEvent(emitter, "touched", key, invalidate),
1035
+ [emitter, key]
1036
+ );
1037
+ return useWatchCore(
1038
+ subscribeFactory,
311
1039
  hasTouchedByPath.bind(null, form, path)
312
1040
  );
313
1041
  }
314
1042
  function useError(form, name) {
315
- return useErrorByPath(form, create$1(name));
1043
+ return useErrorByPath(form, create$1(name))?.message;
316
1044
  }
317
1045
  function useErrorByPath(form, path) {
318
- return useWatch(
319
- form.emitter,
320
- "errors",
321
- getErrorByPath.bind(null, form, path)
1046
+ const { emitter } = form;
1047
+ const { key } = path;
1048
+ const subscribeFactory = React.useCallback(
1049
+ (invalidate) => onKeyEvent(emitter, "errors", key, invalidate),
1050
+ [emitter, key]
1051
+ );
1052
+ return useWatchCore(subscribeFactory, getErrorByPath.bind(null, form, path));
1053
+ }
1054
+ function useFieldErrors(form, name) {
1055
+ return useFieldErrorsByPath(form, create$1(name));
1056
+ }
1057
+ function useFieldErrorsByPath(form, path) {
1058
+ const { emitter } = form;
1059
+ const { key } = path;
1060
+ const subscribeFactory = React.useCallback(
1061
+ (invalidate) => onKeyEvent(emitter, "errors", key, invalidate),
1062
+ [emitter, key]
1063
+ );
1064
+ return useWatchCore(
1065
+ subscribeFactory,
1066
+ getFieldErrorsByPath.bind(null, form, path)
322
1067
  );
323
1068
  }
324
1069
  function useIsDirty(form) {
325
- return useWatch(form.emitter, "touched", isDirty.bind(null, form));
1070
+ return useWatch(form.emitter, "change", isDirty.bind(null, form));
1071
+ }
1072
+ function useDirtyFields(form) {
1073
+ return useWatch(form.emitter, "change", getDirtyFields.bind(null, form));
1074
+ }
1075
+ function useTouchedFields(form) {
1076
+ return useWatch(form.emitter, "touched", getTouchedFields.bind(null, form));
326
1077
  }
327
1078
  function useHasErrors(form) {
328
1079
  return useWatch(form.emitter, "errors", hasErrors.bind(null, form));
@@ -333,6 +1084,31 @@ function useIsSubmitting(form) {
333
1084
  function useSubmitCount(form) {
334
1085
  return useWatch(form.emitter, "submitCount", () => form.submitCount);
335
1086
  }
1087
+ function isEqual(a, b) {
1088
+ if (Object.is(a, b)) return true;
1089
+ if (a instanceof Date && b instanceof Date)
1090
+ return a.getTime() === b.getTime();
1091
+ if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
1092
+ const isArray = Array.isArray(a);
1093
+ if (isArray !== Array.isArray(b)) return false;
1094
+ if (isArray) {
1095
+ if (a.length !== b.length) return false;
1096
+ for (let i = 0; i < a.length; i++) {
1097
+ if (!isEqual(a[i], b[i])) return false;
1098
+ }
1099
+ return true;
1100
+ }
1101
+ const proto = Object.getPrototypeOf(a);
1102
+ if (proto !== Object.prototype && proto !== null) return false;
1103
+ if (Object.getPrototypeOf(b) !== proto) return false;
1104
+ const keysA = Object.keys(a);
1105
+ const keysB = Object.keys(b);
1106
+ if (keysA.length !== keysB.length) return false;
1107
+ for (const key of keysA) {
1108
+ if (!isEqual(a[key], b[key])) return false;
1109
+ }
1110
+ return true;
1111
+ }
336
1112
 
337
1113
  function usePath(name) {
338
1114
  const path = React.useMemo(() => create$1(normalizePath(name)), [name]);
@@ -348,70 +1124,174 @@ function useStageFn(fn) {
348
1124
  const ref = useStage(fn);
349
1125
  return React.useCallback(
350
1126
  (...params) => ref.current(...params),
351
- []
1127
+ [ref]
352
1128
  );
353
1129
  }
354
1130
 
355
- function useValidate(validate, path) {
356
- const form = useFormContext();
1131
+ function useValidate(validate, path, formProp, options) {
1132
+ const contextForm = React.useContext(FormContext);
1133
+ const form = formProp || contextForm;
1134
+ if (!form) throw new Error("no form provided");
357
1135
  const lockRef = React.useRef(null);
358
1136
  const validateRef = React.useRef(validate);
359
1137
  validateRef.current = validate;
1138
+ const debounceRef = React.useRef(options?.debounce ?? 0);
1139
+ debounceRef.current = options?.debounce ?? 0;
360
1140
  React.useEffect(() => {
361
- const validator = () => {
1141
+ let timer = null;
1142
+ let controller = null;
1143
+ let marked = false;
1144
+ const mark = () => {
1145
+ if (marked) return;
1146
+ marked = true;
1147
+ setValidatingByPath(form, path);
1148
+ };
1149
+ const unmark = () => {
1150
+ if (!marked) return;
1151
+ marked = false;
1152
+ unsetValidatingByPath(form, path);
1153
+ };
1154
+ const run = () => {
1155
+ timer = null;
362
1156
  const fn = validateRef.current;
363
- if (!fn) return;
364
- const result = fn(getValueByPath(form, path), { form, path });
1157
+ if (!fn) {
1158
+ unmark();
1159
+ return;
1160
+ }
1161
+ controller?.abort();
1162
+ controller = new AbortController();
1163
+ const lock = lockRef.current = {};
1164
+ let result;
1165
+ try {
1166
+ result = fn(getValueByPath(form, path), {
1167
+ form,
1168
+ path,
1169
+ signal: controller.signal
1170
+ });
1171
+ } catch (e) {
1172
+ unmark();
1173
+ throw e;
1174
+ }
365
1175
  if (!isPromise(result)) {
366
1176
  setErrorByPath(form, path, result);
1177
+ unmark();
367
1178
  return;
368
1179
  }
369
- const lock = lockRef.current = {};
370
- setValidatingByPath(form, path);
371
- result.then((error) => {
372
- if (lock === lockRef.current) {
373
- setErrorByPath(form, path, error);
1180
+ mark();
1181
+ result.then(
1182
+ (error) => {
1183
+ if (lock === lockRef.current) {
1184
+ setErrorByPath(form, path, error);
1185
+ }
374
1186
  }
1187
+ ).catch(() => {
375
1188
  }).finally(() => {
376
1189
  if (lock === lockRef.current) {
377
- unsetValidatingByPath(form, path);
1190
+ unmark();
378
1191
  lockRef.current = null;
379
1192
  }
380
1193
  });
381
1194
  };
1195
+ const validator = () => {
1196
+ const debounce = debounceRef.current;
1197
+ if (debounce > 0) {
1198
+ if (timer !== null) clearTimeout(timer);
1199
+ else mark();
1200
+ timer = setTimeout(run, debounce);
1201
+ return;
1202
+ }
1203
+ run();
1204
+ };
382
1205
  form.validators.set(path.key, validator);
383
1206
  return () => {
384
1207
  form.validators.delete(path.key);
1208
+ if (timer !== null) {
1209
+ clearTimeout(timer);
1210
+ timer = null;
1211
+ }
1212
+ unmark();
1213
+ controller?.abort();
385
1214
  };
386
1215
  }, [form, path.key]);
387
1216
  return useStageFn(() => form.validators.get(path.key)?.());
388
1217
  }
389
1218
 
390
- function useField({
1219
+ function combineRulesAndValidate(rules, validate) {
1220
+ if (!rules) return validate;
1221
+ const ruleValidator = rulesToValidator(rules);
1222
+ if (!validate) return ruleValidator;
1223
+ return (value, meta) => {
1224
+ const ruleErrors = ruleValidator(value, meta);
1225
+ const merge = (other) => {
1226
+ const list = [...ruleErrors ?? []];
1227
+ if (Array.isArray(other)) list.push(...other);
1228
+ else if (other) list.push(other);
1229
+ return list.length ? list : void 0;
1230
+ };
1231
+ const result = validate(value, meta);
1232
+ return isPromise(result) ? result.then(merge) : merge(result);
1233
+ };
1234
+ }
1235
+ function useDelayedErrors(errors, delay) {
1236
+ const [shown, setShown] = React.useState(errors);
1237
+ React.useEffect(() => {
1238
+ if (delay === void 0) return;
1239
+ if (errors.length === 0) {
1240
+ setShown(errors);
1241
+ return;
1242
+ }
1243
+ if (shown.length > 0) {
1244
+ setShown(errors);
1245
+ return;
1246
+ }
1247
+ const timer = setTimeout(() => setShown(errors), delay);
1248
+ return () => clearTimeout(timer);
1249
+ }, [errors, delay, shown]);
1250
+ return delay === void 0 ? errors : shown;
1251
+ }
1252
+ function useFieldCore({
391
1253
  form: f1,
392
1254
  name,
393
1255
  initialValue,
394
1256
  shouldUnregister,
395
1257
  validate,
396
- ...rest
397
- }) {
398
- const f2 = useFormContext();
399
- const form = f1 || f2;
1258
+ rules,
1259
+ validateDebounce,
1260
+ delayError,
1261
+ disabled
1262
+ }, Context) {
1263
+ const contextForm = React.useContext(Context);
1264
+ const form = f1 || contextForm;
1265
+ if (!form) throw new Error("no form provided");
400
1266
  const path = usePath(name);
401
- const validator = useValidate(validate, path);
402
- React.useMemo(() => {
403
- if (initialValue !== void 0) setValueByPath(form, path, initialValue);
404
- }, [form, path]);
405
- const error = useErrorByPath(form, path);
1267
+ const validator = useValidate(
1268
+ combineRulesAndValidate(rules, validate),
1269
+ path,
1270
+ form,
1271
+ {
1272
+ debounce: validateDebounce
1273
+ }
1274
+ );
1275
+ const liveErrors = useFieldErrorsByPath(form, path);
1276
+ const errors = useDelayedErrors(liveErrors, delayError);
1277
+ const errorObject = errors[0];
1278
+ const error = errorObject?.message;
406
1279
  const value = useValueByPath(form, path);
1280
+ const formDisabled = useWatch(form.emitter, "disabled", () => form.disabled);
1281
+ React.useEffect(() => {
1282
+ if (initialValue === void 0) return;
1283
+ if (getValueByPath(form, path) === void 0) {
1284
+ setValueByPath(form, path, initialValue);
1285
+ }
1286
+ }, [form, path, initialValue]);
407
1287
  const onChange = useStageFn((v) => {
408
1288
  setValueByPath(form, path, v);
409
- if (form.validateOnChange || form.revalidateOnChange && error && error !== void 0)
1289
+ if (form.mode === "onChange" || form.mode === "all" || form.mode === "onTouched" && hasTouchedByPath(form, path) || liveErrors.length > 0 && form.reValidateMode === "onChange")
410
1290
  validator();
411
1291
  });
412
1292
  const onBlur = useStageFn(() => {
413
1293
  setTouchedByPath(form, path);
414
- if (form.validateOnBlur || form.revalidateOnBlur && error !== void 0)
1294
+ if (form.mode === "onBlur" || form.mode === "onTouched" || form.mode === "all" || liveErrors.length > 0 && form.reValidateMode === "onBlur")
415
1295
  validator();
416
1296
  });
417
1297
  React.useEffect(
@@ -422,16 +1302,30 @@ function useField({
422
1302
  },
423
1303
  [path, form, shouldUnregister]
424
1304
  );
425
- return { ...rest, value, error, onChange, onBlur, name: path.key };
1305
+ return {
1306
+ form,
1307
+ value,
1308
+ error,
1309
+ errorObject,
1310
+ errors,
1311
+ onChange,
1312
+ onBlur,
1313
+ name: path.key,
1314
+ disabled: formDisabled || !!disabled
1315
+ };
1316
+ }
1317
+ function useField(options) {
1318
+ return useFieldCore(options, FormContext);
426
1319
  }
427
1320
 
428
1321
  let idCounter = 0;
429
1322
  function generateId() {
430
1323
  return `_${++idCounter}`;
431
1324
  }
432
- function useFieldArray(options) {
433
- const f2 = useFormContext();
434
- const form = options.form || f2;
1325
+ function useFieldArrayCore(options, Context) {
1326
+ const contextForm = React.useContext(Context);
1327
+ const form = options.form || contextForm;
1328
+ if (!form) throw new Error("no form provided");
435
1329
  const path = usePath(options.name);
436
1330
  const idsRef = React.useRef([]);
437
1331
  const getArray = React.useCallback(
@@ -459,7 +1353,10 @@ function useFieldArray(options) {
459
1353
  void 0,
460
1354
  computeFields
461
1355
  );
462
- React.useEffect(() => on(form.emitter, "change", syncFields), [form.emitter]);
1356
+ React.useEffect(
1357
+ () => onPathEvent(form.emitter, "change", path, "branch", syncFields),
1358
+ [form.emitter, path]
1359
+ );
463
1360
  const append = useStageFn((value) => {
464
1361
  const arr = getArray();
465
1362
  idsRef.current.push(generateId());
@@ -501,45 +1398,80 @@ function useFieldArray(options) {
501
1398
  newArr.splice(to, 0, item);
502
1399
  setArray(newArr);
503
1400
  });
504
- return { fields, append, prepend, insert, remove, swap, move };
1401
+ const replace = useStageFn((values) => {
1402
+ idsRef.current = values.map(() => generateId());
1403
+ setArray([...values]);
1404
+ });
1405
+ const update = useStageFn((index, value) => {
1406
+ const arr = getArray();
1407
+ if (index < 0 || index >= arr.length) return;
1408
+ const newArr = [...arr];
1409
+ newArr[index] = value;
1410
+ setArray(newArr);
1411
+ });
1412
+ return { fields, append, prepend, insert, remove, swap, move, replace, update };
1413
+ }
1414
+ function useFieldArray(options) {
1415
+ return useFieldArrayCore(options, FormContext);
1416
+ }
1417
+
1418
+ const FormContext = React.createContext(null);
1419
+ const FormProvider = FormContext.Provider;
1420
+ function useFormContext() {
1421
+ const form = React.useContext(FormContext);
1422
+ if (!form) throw new Error("no form provided");
1423
+ return form;
1424
+ }
1425
+ function createFormContext() {
1426
+ const Context = React.createContext(null);
1427
+ function FormProvider2({
1428
+ form,
1429
+ children
1430
+ }) {
1431
+ return React.createElement(Context.Provider, { value: form }, children);
1432
+ }
1433
+ function useFormContext2() {
1434
+ const form = React.useContext(Context);
1435
+ if (!form) throw new Error("no form provided");
1436
+ return form;
1437
+ }
1438
+ function useField(options) {
1439
+ return useFieldCore(options, Context);
1440
+ }
1441
+ function useFieldArray(options) {
1442
+ return useFieldArrayCore(options, Context);
1443
+ }
1444
+ return { FormProvider: FormProvider2, useFormContext: useFormContext2, useField, useFieldArray };
1445
+ }
1446
+ const CheckboxGroupContext = React.createContext(null);
1447
+ const CheckboxGroupProvider = CheckboxGroupContext.Provider;
1448
+ function useCheckboxGroupContext() {
1449
+ const group = React.useContext(CheckboxGroupContext);
1450
+ if (!group) throw new Error("no group provided");
1451
+ return group;
505
1452
  }
506
1453
 
507
1454
  function Form({
508
1455
  form: f1,
509
1456
  initialValues,
1457
+ values,
510
1458
  onSubmit,
511
1459
  onValidSubmit,
512
1460
  onInvalidSubmit,
1461
+ shouldFocusError,
513
1462
  ...props
514
1463
  }) {
515
- const f2 = useForm({ initialValues });
1464
+ const f2 = useForm({ initialValues, values });
516
1465
  const form = f1 || f2;
517
- async function handleSubmit(e) {
518
- e.preventDefault();
519
- setIsSubmitting(form, true);
520
- incrementSubmitCount(form);
521
- const error = await validate(form);
522
- const values = getValues(form);
523
- if (error) {
524
- setIsSubmitting(form, false);
525
- setSubmitSuccessful(form, false);
526
- if (onInvalidSubmit) onInvalidSubmit(getErrors(form), values);
527
- return;
528
- }
529
- try {
530
- if (onSubmit) await onSubmit(values, e);
531
- if (onValidSubmit) onValidSubmit(values, e);
532
- setSubmitSuccessful(form, true);
533
- } catch {
534
- setSubmitSuccessful(form, false);
535
- } finally {
536
- setIsSubmitting(form, false);
537
- }
538
- }
539
- return /* @__PURE__ */ React__namespace.createElement(FormProvider, { value: form }, /* @__PURE__ */ React__namespace.createElement("form", { ...props, noValidate: true, onSubmit: handleSubmit }));
1466
+ const submit = handleSubmit(form, {
1467
+ onSubmit,
1468
+ onValidSubmit,
1469
+ onInvalidSubmit,
1470
+ shouldFocusError
1471
+ });
1472
+ return /* @__PURE__ */ React__namespace.createElement(FormProvider, { value: form }, /* @__PURE__ */ React__namespace.createElement("form", { ...props, noValidate: true, onSubmit: submit }));
540
1473
  }
541
1474
 
542
- const buildInError = /* @__PURE__ */ Symbol("buildInError");
543
1475
  function setRef(ref, value) {
544
1476
  if (typeof ref === "function") {
545
1477
  ref(value);
@@ -547,9 +1479,30 @@ function setRef(ref, value) {
547
1479
  ref.current = value;
548
1480
  }
549
1481
  }
1482
+ function errorIdFromKey(key) {
1483
+ const id = key.replace(/["'[\],\s]+/g, "-").replace(/^-+|-+$/g, "");
1484
+ return id || "field";
1485
+ }
550
1486
  const Field = React__namespace.forwardRef(
551
- ({ validate, eventToValue, initialValue, name, asProps, ...props }, ref) => {
1487
+ ({
1488
+ validate,
1489
+ eventToValue,
1490
+ initialValue,
1491
+ name,
1492
+ asProps,
1493
+ renderError,
1494
+ as,
1495
+ valueToProps,
1496
+ form: formProp,
1497
+ shouldUnregister,
1498
+ rules,
1499
+ validateDebounce,
1500
+ disabled,
1501
+ delayError,
1502
+ ...props
1503
+ }, ref) => {
552
1504
  const innerRef = React__namespace.useRef(null);
1505
+ const [nativeInvalidCount, setNativeInvalidCount] = React__namespace.useState(0);
553
1506
  const mergedRef = React__namespace.useCallback(
554
1507
  (node) => {
555
1508
  innerRef.current = node;
@@ -557,57 +1510,187 @@ const Field = React__namespace.forwardRef(
557
1510
  },
558
1511
  [ref]
559
1512
  );
560
- const { as, value, valueToProps, onChange, error, ...rest } = useField({
561
- ...props,
1513
+ const {
1514
+ value,
1515
+ onChange,
1516
+ onBlur,
1517
+ error,
1518
+ form,
1519
+ name: fieldKey,
1520
+ disabled: isDisabled
1521
+ } = useField({
562
1522
  name,
1523
+ form: formProp,
563
1524
  initialValue,
1525
+ shouldUnregister,
1526
+ rules,
1527
+ validateDebounce,
1528
+ delayError,
1529
+ disabled,
564
1530
  validate: (...params) => {
565
- if (innerRef.current?.checkValidity() === false)
566
- return buildInError;
1531
+ const el = innerRef.current;
1532
+ if (el && typeof el.checkValidity === "function") {
1533
+ el.setCustomValidity("");
1534
+ if (el.checkValidity() === false) {
1535
+ setNativeInvalidCount((count) => count + 1);
1536
+ return void 0;
1537
+ }
1538
+ }
567
1539
  if (validate) return validate(...params);
568
1540
  }
569
1541
  });
570
1542
  const Component = as || "input";
571
1543
  React__namespace.useEffect(() => {
572
- if (!innerRef.current) return;
573
- if (error === buildInError) {
574
- innerRef.current.setCustomValidity("");
575
- innerRef.current.reportValidity();
576
- return;
577
- }
1544
+ const el = innerRef.current;
1545
+ if (!el || typeof el.setCustomValidity !== "function") return;
578
1546
  if (typeof error === "string") {
579
- innerRef.current.setCustomValidity(error);
580
- innerRef.current.reportValidity();
1547
+ el.setCustomValidity(error);
1548
+ el.reportValidity();
1549
+ } else {
1550
+ el.setCustomValidity("");
581
1551
  }
582
1552
  }, [error]);
1553
+ React__namespace.useEffect(() => {
1554
+ if (nativeInvalidCount > 0) innerRef.current?.reportValidity();
1555
+ }, [nativeInvalidCount]);
1556
+ React__namespace.useEffect(
1557
+ () => on(
1558
+ form.emitter,
1559
+ "focusError",
1560
+ (key, options) => {
1561
+ if (key !== fieldKey) return;
1562
+ const el = innerRef.current;
1563
+ if (!el || typeof el.focus !== "function") return;
1564
+ el.focus();
1565
+ if (options?.shouldSelect && typeof el.select === "function") {
1566
+ el.select();
1567
+ }
1568
+ }
1569
+ ),
1570
+ [form, fieldKey]
1571
+ );
583
1572
  const toValue = eventToValue ?? ((e) => e.target.value);
584
- return /* @__PURE__ */ React__namespace.createElement(
1573
+ const errorId = errorIdFromKey(fieldKey);
1574
+ return /* @__PURE__ */ React__namespace.createElement(React__namespace.Fragment, null, /* @__PURE__ */ React__namespace.createElement(
585
1575
  Component,
586
1576
  {
587
- ...rest,
1577
+ "aria-invalid": error ? true : void 0,
1578
+ "aria-describedby": error && renderError ? errorId : void 0,
1579
+ ...props,
1580
+ name: fieldKey,
1581
+ onBlur,
588
1582
  ...asProps,
589
1583
  ...valueToProps ? valueToProps(value) : { value },
1584
+ disabled: isDisabled,
590
1585
  onChange: (e) => onChange(toValue(e)),
591
1586
  ref: mergedRef
592
1587
  }
593
- );
1588
+ ), error && renderError ? /* @__PURE__ */ React__namespace.createElement("span", { id: errorId, role: "alert" }, renderError(error, errorId)) : null);
594
1589
  }
595
1590
  );
596
1591
  const Checkbox = React__namespace.forwardRef(
597
- ({ name, ...props }, ref) => {
598
- const { value, onChange, error, ...rest } = useField({ ...props, name });
1592
+ ({
1593
+ name,
1594
+ form,
1595
+ initialValue,
1596
+ shouldUnregister,
1597
+ validate,
1598
+ rules,
1599
+ validateDebounce,
1600
+ disabled,
1601
+ delayError,
1602
+ ...props
1603
+ }, ref) => {
1604
+ const {
1605
+ value,
1606
+ onChange,
1607
+ onBlur,
1608
+ error,
1609
+ name: fieldKey,
1610
+ disabled: isDisabled
1611
+ } = useField({
1612
+ name,
1613
+ form,
1614
+ initialValue,
1615
+ shouldUnregister,
1616
+ validate,
1617
+ rules,
1618
+ validateDebounce,
1619
+ delayError,
1620
+ disabled
1621
+ });
599
1622
  return /* @__PURE__ */ React__namespace.createElement(
600
1623
  "input",
601
1624
  {
602
- ...rest,
1625
+ "aria-invalid": error ? true : void 0,
1626
+ ...props,
1627
+ name: fieldKey,
1628
+ onBlur,
603
1629
  type: "checkbox",
604
1630
  checked: !!value,
1631
+ disabled: isDisabled,
605
1632
  onChange: (e) => onChange(e.target.checked),
606
1633
  ref
607
1634
  }
608
1635
  );
609
1636
  }
610
1637
  );
1638
+ function toSelectValue(multiple, value) {
1639
+ if (multiple) return Array.isArray(value) ? value : [];
1640
+ return value ?? "";
1641
+ }
1642
+ const Select = React__namespace.forwardRef(
1643
+ ({
1644
+ name,
1645
+ multiple,
1646
+ children,
1647
+ form,
1648
+ initialValue,
1649
+ shouldUnregister,
1650
+ validate,
1651
+ rules,
1652
+ validateDebounce,
1653
+ disabled,
1654
+ delayError,
1655
+ ...props
1656
+ }, ref) => {
1657
+ const {
1658
+ value,
1659
+ onChange,
1660
+ onBlur,
1661
+ error,
1662
+ name: fieldKey,
1663
+ disabled: isDisabled
1664
+ } = useField({
1665
+ name,
1666
+ form,
1667
+ initialValue,
1668
+ shouldUnregister,
1669
+ validate,
1670
+ rules,
1671
+ validateDebounce,
1672
+ delayError,
1673
+ disabled
1674
+ });
1675
+ return /* @__PURE__ */ React__namespace.createElement(
1676
+ "select",
1677
+ {
1678
+ "aria-invalid": error ? true : void 0,
1679
+ ...props,
1680
+ name: fieldKey,
1681
+ onBlur,
1682
+ multiple,
1683
+ value: toSelectValue(multiple, value),
1684
+ disabled: isDisabled,
1685
+ onChange: (e) => onChange(
1686
+ multiple ? Array.from(e.target.selectedOptions, (option) => option.value) : e.target.value
1687
+ ),
1688
+ ref
1689
+ },
1690
+ children
1691
+ );
1692
+ }
1693
+ );
611
1694
 
612
1695
  exports.Checkbox = Checkbox;
613
1696
  exports.CheckboxGroupContext = CheckboxGroupContext;
@@ -616,16 +1699,25 @@ exports.Field = Field;
616
1699
  exports.Form = Form;
617
1700
  exports.FormContext = FormContext;
618
1701
  exports.FormProvider = FormProvider;
1702
+ exports.Select = Select;
1703
+ exports.VALIDATION_OUTCOME = VALIDATION_OUTCOME;
619
1704
  exports.clearErrors = clearErrors;
620
1705
  exports.createForm = create;
1706
+ exports.createFormContext = createFormContext;
621
1707
  exports.ensureValidate = ensureValidate;
1708
+ exports.getDirtyFields = getDirtyFields;
622
1709
  exports.getError = getError;
623
1710
  exports.getErrorByPath = getErrorByPath;
624
1711
  exports.getErrors = getErrors;
1712
+ exports.getFieldErrors = getFieldErrors;
1713
+ exports.getFieldErrorsByPath = getFieldErrorsByPath;
1714
+ exports.getFieldState = getFieldState;
625
1715
  exports.getFirstError = getFirstError;
1716
+ exports.getTouchedFields = getTouchedFields;
626
1717
  exports.getValue = getValue;
627
1718
  exports.getValueByPath = getValueByPath;
628
1719
  exports.getValues = getValues;
1720
+ exports.handleSubmit = handleSubmit;
629
1721
  exports.hasErrors = hasErrors;
630
1722
  exports.hasTouched = hasTouched;
631
1723
  exports.hasTouchedByPath = hasTouchedByPath;
@@ -635,8 +1727,11 @@ exports.isTouched = isTouched;
635
1727
  exports.removeField = removeField;
636
1728
  exports.removeFieldByPath = removeFieldByPath;
637
1729
  exports.reset = reset;
1730
+ exports.resetField = resetField;
1731
+ exports.setDisabled = setDisabled;
638
1732
  exports.setError = setError;
639
1733
  exports.setErrorByPath = setErrorByPath;
1734
+ exports.setFocus = setFocus;
640
1735
  exports.setInitialValues = setInitialValues;
641
1736
  exports.setIsSubmitting = setIsSubmitting;
642
1737
  exports.setSubmitSuccessful = setSubmitSuccessful;
@@ -645,13 +1740,17 @@ exports.setTouchedByPath = setTouchedByPath;
645
1740
  exports.setValidatingByPath = setValidatingByPath;
646
1741
  exports.setValue = setValue;
647
1742
  exports.setValueByPath = setValueByPath;
1743
+ exports.subscribe = subscribe;
648
1744
  exports.trigger = trigger;
649
1745
  exports.unsetValidatingByPath = unsetValidatingByPath;
650
1746
  exports.useCheckboxGroupContext = useCheckboxGroupContext;
1747
+ exports.useDirtyFields = useDirtyFields;
651
1748
  exports.useError = useError;
652
1749
  exports.useErrorByPath = useErrorByPath;
653
1750
  exports.useField = useField;
654
1751
  exports.useFieldArray = useFieldArray;
1752
+ exports.useFieldErrors = useFieldErrors;
1753
+ exports.useFieldErrorsByPath = useFieldErrorsByPath;
655
1754
  exports.useForm = useForm;
656
1755
  exports.useFormContext = useFormContext;
657
1756
  exports.useHasErrors = useHasErrors;
@@ -660,6 +1759,7 @@ exports.useIsSubmitting = useIsSubmitting;
660
1759
  exports.useSubmitCount = useSubmitCount;
661
1760
  exports.useTouched = useTouched;
662
1761
  exports.useTouchedByPath = useTouchedByPath;
1762
+ exports.useTouchedFields = useTouchedFields;
663
1763
  exports.useValue = useValue;
664
1764
  exports.useValueByPath = useValueByPath;
665
1765
  exports.useWatch = useWatch;