solid-hook-form 1.9.4 → 2.0.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/dist/main.d.ts CHANGED
@@ -36,12 +36,13 @@ type RegisterReturn<F extends FormValues> = {
36
36
  ref(ref: Ref): void;
37
37
  onInput(event: Event | unknown): void;
38
38
  onChange(event: Event | unknown): void;
39
+ onBlur(event: FocusEvent | unknown): void;
39
40
  };
40
41
  type Register<F extends FormValues> = (name: Path<F>, options?: Rules<F, Path<F>>) => RegisterReturn<F>;
41
42
 
42
43
  type Control<F extends FormValues> = {
43
44
  values: Accessor<F>;
44
- errors: Accessor<FieldErrors<F>>;
45
+ errors: FieldErrors<F>;
45
46
  register: Register<F>;
46
47
  };
47
48
  type UseControllerArg<F extends FormValues> = {
@@ -65,37 +66,47 @@ type ControllerProps<F extends FormValues> = {
65
66
  render(arg: UseControllerReturn<F>): JSXElement;
66
67
  };
67
68
 
69
+ type TouchedFields<F extends FormValues = FormValues> = Partial<Record<Path<F>, boolean>>;
70
+
71
+ type DirtyFields<F extends FormValues = FormValues> = Partial<Record<Path<F>, boolean>>;
72
+
68
73
  type FormValues = Record<string, any>;
69
74
  type GetValues<F extends FormValues> = {
70
75
  (): F;
71
76
  <N extends FieldPath<F>>(name: N): FieldPathValue<F, N>;
72
77
  };
73
78
  type SetValue<F extends FormValues> = (name: Path<F>, value: FieldPathValue<F, Path<F>>) => void;
74
- type SubmitCallback<F extends FormValues> = (values: F) => void;
75
- type OnSubmit<F extends FormValues> = (submit: SubmitCallback<F>) => (event: SubmitEvent) => void;
76
- type Reset<F extends FormValues> = (newDefaultValues?: Partial<F>) => void;
79
+ type SubmitHandler<F extends FormValues> = (values: F) => void;
80
+ type SubmitErrorHandler<F extends FormValues> = (errors: FieldErrors<F>) => void;
81
+ type HandleSubmit<F extends FormValues> = (onSubmit: SubmitHandler<F>, onError?: SubmitErrorHandler<F>) => (event: SubmitEvent) => void;
82
+ type ResetOptions = {
83
+ keepTouched?: boolean;
84
+ keepDirty?: boolean;
85
+ };
86
+ type Reset<F extends FormValues> = (newDefaultValues?: Partial<F>, options?: ResetOptions) => void;
77
87
  type CreateFormArg<F extends FormValues> = {
78
88
  defaultValues: F;
79
- mode?: "onChange" | "onSubmit";
89
+ mode?: "onChange" | "onSubmit" | "onBlur";
80
90
  resolver?: Resolver<F>;
81
91
  };
82
92
  type CreateFormReturn<F extends FormValues = FormValues> = {
83
93
  control: Control<F>;
84
94
  formState: {
85
- errors: Accessor<FieldErrors<F>>;
95
+ errors: FieldErrors<F>;
86
96
  isValid: Accessor<boolean>;
97
+ isDirty: Accessor<boolean>;
98
+ touchedFields: Accessor<TouchedFields<F>>;
99
+ dirtyFields: Accessor<DirtyFields<F>>;
87
100
  };
88
101
  values: Accessor<F>;
89
- errors: Accessor<FieldErrors<F>>;
90
- isValid: Accessor<boolean>;
102
+ errors: FieldErrors<F>;
91
103
  register: Register<F>;
92
104
  getValues: GetValues<F>;
93
105
  setValue: SetValue<F>;
94
- onSubmit: OnSubmit<F>;
95
- handleSubmit: OnSubmit<F>;
106
+ handleSubmit: HandleSubmit<F>;
96
107
  reset: Reset<F>;
97
108
  };
98
- type CreateForm = <F extends FormValues>(arg?: CreateFormArg<F>) => CreateFormReturn<F>;
109
+ type CreateForm = <F extends FormValues>(arg: CreateFormArg<F>) => CreateFormReturn<F>;
99
110
 
100
111
  declare const createForm: CreateForm;
101
112
 
@@ -114,4 +125,4 @@ declare const get: (object: any, path: string, defaultValue?: unknown) => any;
114
125
 
115
126
  declare const set: (object: FormValues, path: string, value: unknown) => void;
116
127
 
117
- export { type Control, Controller, type CreateFormArg, type CreateFormReturn, type FieldError, type FieldErrors, FormProvider, type FormValues, type Rules, type UseControllerArg, type UseControllerReturn, createForm, get, set, useController, createForm as useForm, useFormContext };
128
+ export { type Control, Controller, type CreateFormArg, type CreateFormReturn, type FieldError, type FieldErrors, FormProvider, type FormValues, type Rules, type SubmitErrorHandler, type SubmitHandler, type UseControllerArg, type UseControllerReturn, createForm, get, set, useController, useFormContext };
package/dist/main.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createContext, createSignal, createMemo, useContext } from 'solid-js';
2
+ import { createStore, reconcile, produce } from 'solid-js/store';
2
3
  import { memo, createComponent } from 'solid-js/web';
3
4
 
4
5
  // src/create_form.ts
@@ -24,6 +25,12 @@ var getFieldValue = (event) => {
24
25
 
25
26
  // src/logic/set_value.ts
26
27
  var setFieldValue = (field, value) => {
28
+ if (!field) {
29
+ return;
30
+ }
31
+ if (value === void 0) {
32
+ return;
33
+ }
27
34
  if (field instanceof HTMLSelectElement) {
28
35
  field.value = value;
29
36
  return;
@@ -106,25 +113,26 @@ var validate = (values, name, rules = {}) => {
106
113
  }
107
114
  };
108
115
  var createErrors = () => {
109
- const [errors, setErrors] = createSignal({});
116
+ const [errors, setErrors] = createStore({});
110
117
  const getError = (name) => {
111
- return get(errors(), name);
118
+ return errors[name];
112
119
  };
113
120
  const appendError = (name, error) => {
114
- setErrors((prev) => {
115
- const newState = { ...prev, [name]: error };
116
- return newState;
117
- });
121
+ setErrors(
122
+ produce((prevState) => {
123
+ prevState[name] = error;
124
+ })
125
+ );
118
126
  };
119
127
  const removeError = (name) => {
120
- setErrors((prev) => {
121
- const newState = { ...prev };
122
- delete newState[name];
123
- return newState;
124
- });
128
+ setErrors(
129
+ produce((prevState) => {
130
+ delete prevState[name];
131
+ })
132
+ );
125
133
  };
126
134
  const resetErrors = () => {
127
- setErrors({});
135
+ setErrors(reconcile({}));
128
136
  };
129
137
  return {
130
138
  errors,
@@ -213,16 +221,65 @@ var getResolverFields = (fields) => {
213
221
  return acc;
214
222
  }, {});
215
223
  };
224
+ var createTouchedFields = () => {
225
+ const [touchedFields, setTouchedFields] = createSignal({});
226
+ const addTouched = (name) => {
227
+ setTouchedFields((prev) => {
228
+ const newState = { ...prev };
229
+ set(newState, name, true);
230
+ return newState;
231
+ });
232
+ };
233
+ const resetTouched = (keepTouched) => {
234
+ if (keepTouched) {
235
+ return;
236
+ }
237
+ setTouchedFields({});
238
+ };
239
+ return { touchedFields, addTouched, resetTouched };
240
+ };
241
+ var isSomeFieldDirty = (value) => {
242
+ return Object.values(value).some((value2) => {
243
+ if (typeof value2 === "object") {
244
+ return isSomeFieldDirty(value2);
245
+ }
246
+ return value2;
247
+ });
248
+ };
249
+ var createDirtyFields = (defaultValues) => {
250
+ const [dirtyFields, setDirtyFields] = createSignal({});
251
+ const isDirty = createMemo(() => {
252
+ return isSomeFieldDirty(dirtyFields());
253
+ });
254
+ const checkDirty = (name, value) => {
255
+ const defaultValue = get(defaultValues, name);
256
+ const isDirty2 = value !== defaultValue;
257
+ setDirtyFields((prev) => {
258
+ const newState = { ...prev };
259
+ set(newState, name, isDirty2);
260
+ return newState;
261
+ });
262
+ };
263
+ const resetDirty = (keepDirty) => {
264
+ if (keepDirty) {
265
+ return;
266
+ }
267
+ setDirtyFields({});
268
+ };
269
+ return { dirtyFields, isDirty, checkDirty, resetDirty };
270
+ };
216
271
 
217
272
  // src/create_form.ts
218
- var createForm = (arg = { defaultValues: {} }) => {
273
+ var createForm = (arg) => {
219
274
  const { defaultValues, mode = "onChange", resolver } = arg;
220
275
  const { fields, getField, setField } = createFields();
221
276
  const { rules, addRule, getRule } = createRules();
222
- const [values, setValues] = createSignal(defaultValues);
277
+ const [values, setValues] = createSignal(structuredClone(defaultValues));
223
278
  const { errors, appendError, removeError, resetErrors, getError } = createErrors();
279
+ const { touchedFields, addTouched, resetTouched } = createTouchedFields();
280
+ const { dirtyFields, isDirty, checkDirty, resetDirty } = createDirtyFields(defaultValues);
224
281
  const isValid = createMemo(() => {
225
- return !Object.keys(errors()).length;
282
+ return !Object.keys(errors).length;
226
283
  });
227
284
  const setFieldError = (name, error) => {
228
285
  const field = getField(name);
@@ -238,7 +295,7 @@ var createForm = (arg = { defaultValues: {} }) => {
238
295
  if (!resolver) {
239
296
  return;
240
297
  }
241
- const result = await resolver(values(), null, {
298
+ const result = await resolver(values() || {}, null, {
242
299
  fields: getResolverFields(fields),
243
300
  shouldUseNativeValidation: false
244
301
  });
@@ -284,13 +341,15 @@ var createForm = (arg = { defaultValues: {} }) => {
284
341
  }
285
342
  };
286
343
  const onFieldChange = (event, name) => {
287
- const value = formatValue(getFieldValue(event), rules[name]);
344
+ const fieldValue = getFieldValue(event);
345
+ const value = formatValue(fieldValue, rules[name]);
288
346
  setValues((prev) => {
289
347
  const newState = { ...prev };
290
348
  set(newState, name, value);
291
349
  return newState;
292
350
  });
293
351
  validateField(name);
352
+ checkDirty(name, value);
294
353
  };
295
354
  const register = (name, options = {}) => {
296
355
  addRule(name, options);
@@ -306,15 +365,19 @@ var createForm = (arg = { defaultValues: {} }) => {
306
365
  onFieldChange(event, name);
307
366
  }
308
367
  },
368
+ onBlur(event) {
369
+ if (mode === "onBlur") {
370
+ onFieldChange(event, name);
371
+ }
372
+ addTouched(name);
373
+ },
309
374
  ref(element) {
310
375
  const field = getField(name);
311
376
  if (field) {
312
377
  return;
313
378
  }
314
379
  setField(name, element);
315
- if (element) {
316
- setFieldValue(element, get(values(), name));
317
- }
380
+ setFieldValue(element, get(values(), name));
318
381
  }
319
382
  };
320
383
  };
@@ -336,47 +399,45 @@ var createForm = (arg = { defaultValues: {} }) => {
336
399
  return newValues;
337
400
  });
338
401
  const field = getField(name);
339
- if (field) {
340
- setFieldValue(field, value);
341
- }
402
+ setFieldValue(field, value);
342
403
  };
343
- const onSubmit = (submit) => {
404
+ const handleSubmit = (onSubmit, onError) => {
344
405
  return async (event) => {
345
406
  event.preventDefault();
346
407
  await validateAllFields();
347
408
  if (isValid()) {
348
- submit(getValues());
409
+ onSubmit(getValues());
410
+ return;
349
411
  }
412
+ onError?.(errors);
350
413
  focusFirstError();
351
414
  };
352
415
  };
353
- const reset = (newDefaultValues = {}) => {
354
- const newValues = {
355
- ...structuredClone(defaultValues),
356
- ...newDefaultValues
357
- };
416
+ const reset = (values2, options = {}) => {
417
+ const newValues = values2 ? values2 : structuredClone(defaultValues);
358
418
  setValues(() => newValues);
359
419
  resetErrors();
420
+ resetTouched(options.keepTouched);
421
+ resetDirty(options.keepDirty);
360
422
  Object.entries(fields).forEach(([name, field]) => {
361
- if (field) {
362
- setFieldValue(field, get(newValues, name));
363
- }
423
+ setFieldValue(field, get(newValues, name));
364
424
  });
365
425
  };
366
426
  return {
367
427
  control,
368
428
  formState: {
369
429
  errors,
370
- isValid
430
+ isValid,
431
+ isDirty,
432
+ touchedFields,
433
+ dirtyFields
371
434
  },
372
435
  values,
373
436
  errors,
374
- isValid,
375
437
  register,
376
438
  getValues,
377
439
  setValue,
378
- onSubmit,
379
- handleSubmit: onSubmit,
440
+ handleSubmit,
380
441
  reset
381
442
  };
382
443
  };
@@ -397,12 +458,12 @@ var useController = (arg) => {
397
458
  return get(control.values(), arg.name);
398
459
  });
399
460
  const error = createMemo(() => {
400
- return control.errors()[arg.name];
461
+ return control.errors[arg.name];
401
462
  });
402
463
  return {
403
464
  field: {
404
- value,
405
- ...fieldProps
465
+ ...fieldProps,
466
+ value
406
467
  },
407
468
  fieldState: {
408
469
  error
@@ -424,4 +485,4 @@ var FormProvider = (props) => {
424
485
  });
425
486
  };
426
487
 
427
- export { Controller, FormProvider, createForm, get, set, useController, createForm as useForm, useFormContext };
488
+ export { Controller, FormProvider, createForm, get, set, useController, useFormContext };
package/dist/main.jsx CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/create_form.ts
2
- import { createMemo, createSignal as createSignal2 } from "solid-js";
2
+ import { createMemo as createMemo2, createSignal as createSignal3 } from "solid-js";
3
3
 
4
4
  // src/logic/get_value.ts
5
5
  var getFieldValue = (event) => {
@@ -22,6 +22,12 @@ var getFieldValue = (event) => {
22
22
 
23
23
  // src/logic/set_value.ts
24
24
  var setFieldValue = (field, value) => {
25
+ if (!field) {
26
+ return;
27
+ }
28
+ if (value === void 0) {
29
+ return;
30
+ }
25
31
  if (field instanceof HTMLSelectElement) {
26
32
  field.value = value;
27
33
  return;
@@ -105,27 +111,28 @@ var validate = (values, name, rules = {}) => {
105
111
  };
106
112
 
107
113
  // src/logic/create_errors.ts
108
- import { createSignal } from "solid-js";
114
+ import { createStore, reconcile, produce } from "solid-js/store";
109
115
  var createErrors = () => {
110
- const [errors, setErrors] = createSignal({});
116
+ const [errors, setErrors] = createStore({});
111
117
  const getError = (name) => {
112
- return get(errors(), name);
118
+ return errors[name];
113
119
  };
114
120
  const appendError = (name, error) => {
115
- setErrors((prev) => {
116
- const newState = { ...prev, [name]: error };
117
- return newState;
118
- });
121
+ setErrors(
122
+ produce((prevState) => {
123
+ prevState[name] = error;
124
+ })
125
+ );
119
126
  };
120
127
  const removeError = (name) => {
121
- setErrors((prev) => {
122
- const newState = { ...prev };
123
- delete newState[name];
124
- return newState;
125
- });
128
+ setErrors(
129
+ produce((prevState) => {
130
+ delete prevState[name];
131
+ })
132
+ );
126
133
  };
127
134
  const resetErrors = () => {
128
- setErrors({});
135
+ setErrors(reconcile({}));
129
136
  };
130
137
  return {
131
138
  errors,
@@ -215,15 +222,70 @@ var getResolverFields = (fields) => {
215
222
  }, {});
216
223
  };
217
224
 
225
+ // src/logic/create_touched_fields.ts
226
+ import { createSignal } from "solid-js";
227
+ var createTouchedFields = () => {
228
+ const [touchedFields, setTouchedFields] = createSignal({});
229
+ const addTouched = (name) => {
230
+ setTouchedFields((prev) => {
231
+ const newState = { ...prev };
232
+ set(newState, name, true);
233
+ return newState;
234
+ });
235
+ };
236
+ const resetTouched = (keepTouched) => {
237
+ if (keepTouched) {
238
+ return;
239
+ }
240
+ setTouchedFields({});
241
+ };
242
+ return { touchedFields, addTouched, resetTouched };
243
+ };
244
+
245
+ // src/logic/create_dirty_fields.ts
246
+ import { createMemo, createSignal as createSignal2 } from "solid-js";
247
+ var isSomeFieldDirty = (value) => {
248
+ return Object.values(value).some((value2) => {
249
+ if (typeof value2 === "object") {
250
+ return isSomeFieldDirty(value2);
251
+ }
252
+ return value2;
253
+ });
254
+ };
255
+ var createDirtyFields = (defaultValues) => {
256
+ const [dirtyFields, setDirtyFields] = createSignal2({});
257
+ const isDirty = createMemo(() => {
258
+ return isSomeFieldDirty(dirtyFields());
259
+ });
260
+ const checkDirty = (name, value) => {
261
+ const defaultValue = get(defaultValues, name);
262
+ const isDirty2 = value !== defaultValue;
263
+ setDirtyFields((prev) => {
264
+ const newState = { ...prev };
265
+ set(newState, name, isDirty2);
266
+ return newState;
267
+ });
268
+ };
269
+ const resetDirty = (keepDirty) => {
270
+ if (keepDirty) {
271
+ return;
272
+ }
273
+ setDirtyFields({});
274
+ };
275
+ return { dirtyFields, isDirty, checkDirty, resetDirty };
276
+ };
277
+
218
278
  // src/create_form.ts
219
- var createForm = (arg = { defaultValues: {} }) => {
279
+ var createForm = (arg) => {
220
280
  const { defaultValues, mode = "onChange", resolver } = arg;
221
281
  const { fields, getField, setField } = createFields();
222
282
  const { rules, addRule, getRule } = createRules();
223
- const [values, setValues] = createSignal2(defaultValues);
283
+ const [values, setValues] = createSignal3(structuredClone(defaultValues));
224
284
  const { errors, appendError, removeError, resetErrors, getError } = createErrors();
225
- const isValid = createMemo(() => {
226
- return !Object.keys(errors()).length;
285
+ const { touchedFields, addTouched, resetTouched } = createTouchedFields();
286
+ const { dirtyFields, isDirty, checkDirty, resetDirty } = createDirtyFields(defaultValues);
287
+ const isValid = createMemo2(() => {
288
+ return !Object.keys(errors).length;
227
289
  });
228
290
  const setFieldError = (name, error) => {
229
291
  const field = getField(name);
@@ -239,7 +301,7 @@ var createForm = (arg = { defaultValues: {} }) => {
239
301
  if (!resolver) {
240
302
  return;
241
303
  }
242
- const result = await resolver(values(), null, {
304
+ const result = await resolver(values() || {}, null, {
243
305
  fields: getResolverFields(fields),
244
306
  shouldUseNativeValidation: false
245
307
  });
@@ -285,13 +347,15 @@ var createForm = (arg = { defaultValues: {} }) => {
285
347
  }
286
348
  };
287
349
  const onFieldChange = (event, name) => {
288
- const value = formatValue(getFieldValue(event), rules[name]);
350
+ const fieldValue = getFieldValue(event);
351
+ const value = formatValue(fieldValue, rules[name]);
289
352
  setValues((prev) => {
290
353
  const newState = { ...prev };
291
354
  set(newState, name, value);
292
355
  return newState;
293
356
  });
294
357
  validateField(name);
358
+ checkDirty(name, value);
295
359
  };
296
360
  const register = (name, options = {}) => {
297
361
  addRule(name, options);
@@ -307,15 +371,19 @@ var createForm = (arg = { defaultValues: {} }) => {
307
371
  onFieldChange(event, name);
308
372
  }
309
373
  },
374
+ onBlur(event) {
375
+ if (mode === "onBlur") {
376
+ onFieldChange(event, name);
377
+ }
378
+ addTouched(name);
379
+ },
310
380
  ref(element) {
311
381
  const field = getField(name);
312
382
  if (field) {
313
383
  return;
314
384
  }
315
385
  setField(name, element);
316
- if (element) {
317
- setFieldValue(element, get(values(), name));
318
- }
386
+ setFieldValue(element, get(values(), name));
319
387
  }
320
388
  };
321
389
  };
@@ -337,53 +405,51 @@ var createForm = (arg = { defaultValues: {} }) => {
337
405
  return newValues;
338
406
  });
339
407
  const field = getField(name);
340
- if (field) {
341
- setFieldValue(field, value);
342
- }
408
+ setFieldValue(field, value);
343
409
  };
344
- const onSubmit = (submit) => {
410
+ const handleSubmit = (onSubmit, onError) => {
345
411
  return async (event) => {
346
412
  event.preventDefault();
347
413
  await validateAllFields();
348
414
  if (isValid()) {
349
- submit(getValues());
415
+ onSubmit(getValues());
416
+ return;
350
417
  }
418
+ onError?.(errors);
351
419
  focusFirstError();
352
420
  };
353
421
  };
354
- const reset = (newDefaultValues = {}) => {
355
- const newValues = {
356
- ...structuredClone(defaultValues),
357
- ...newDefaultValues
358
- };
422
+ const reset = (values2, options = {}) => {
423
+ const newValues = values2 ? values2 : structuredClone(defaultValues);
359
424
  setValues(() => newValues);
360
425
  resetErrors();
426
+ resetTouched(options.keepTouched);
427
+ resetDirty(options.keepDirty);
361
428
  Object.entries(fields).forEach(([name, field]) => {
362
- if (field) {
363
- setFieldValue(field, get(newValues, name));
364
- }
429
+ setFieldValue(field, get(newValues, name));
365
430
  });
366
431
  };
367
432
  return {
368
433
  control,
369
434
  formState: {
370
435
  errors,
371
- isValid
436
+ isValid,
437
+ isDirty,
438
+ touchedFields,
439
+ dirtyFields
372
440
  },
373
441
  values,
374
442
  errors,
375
- isValid,
376
443
  register,
377
444
  getValues,
378
445
  setValue,
379
- onSubmit,
380
- handleSubmit: onSubmit,
446
+ handleSubmit,
381
447
  reset
382
448
  };
383
449
  };
384
450
 
385
451
  // src/use_controller.ts
386
- import { createMemo as createMemo2 } from "solid-js";
452
+ import { createMemo as createMemo3 } from "solid-js";
387
453
 
388
454
  // src/use_form_context.ts
389
455
  import { useContext } from "solid-js";
@@ -403,16 +469,16 @@ var useController = (arg) => {
403
469
  const form = useFormContext();
404
470
  const control = form?.control || arg.control;
405
471
  const fieldProps = control.register(arg.name, arg.rules);
406
- const value = createMemo2(() => {
472
+ const value = createMemo3(() => {
407
473
  return get(control.values(), arg.name);
408
474
  });
409
- const error = createMemo2(() => {
410
- return control.errors()[arg.name];
475
+ const error = createMemo3(() => {
476
+ return control.errors[arg.name];
411
477
  });
412
478
  return {
413
479
  field: {
414
- value,
415
- ...fieldProps
480
+ ...fieldProps,
481
+ value
416
482
  },
417
483
  fieldState: {
418
484
  error
@@ -437,6 +503,5 @@ export {
437
503
  get,
438
504
  set,
439
505
  useController,
440
- createForm as useForm,
441
506
  useFormContext
442
507
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solid-hook-form",
3
- "version": "1.9.4",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "main": "./dist/main.js",
6
6
  "module": "./dist/main.js",
@@ -26,10 +26,7 @@
26
26
  "typescript": "~5.7.2"
27
27
  },
28
28
  "scripts": {
29
- "preinstall": "cd ./tests && npm ci",
30
- "build": "tsc && tsup",
31
- "test": "cd ./tests && npm run test",
32
- "test:ui": "cd ./tests && npm run test:ui"
29
+ "build": "tsc && tsup"
33
30
  },
34
31
  "author": "thorn_pear",
35
32
  "license": "ISC",