@strictly/react-form 0.0.18 → 0.0.19

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 (37) hide show
  1. package/.out/core/mobx/form_model.d.ts +12 -9
  2. package/.out/core/mobx/form_model.js +103 -137
  3. package/.out/core/mobx/hooks.js +3 -4
  4. package/.out/core/mobx/merge_field_adapters_with_two_way_converter.d.ts +1 -1
  5. package/.out/core/mobx/merge_field_adapters_with_validators.js +5 -1
  6. package/.out/core/mobx/specs/form_model.tests.js +28 -23
  7. package/.out/mantine/create_fields_view.d.ts +2 -1
  8. package/.out/mantine/hooks.d.ts +6 -5
  9. package/.out/mantine/specs/checkbox_hooks.stories.d.ts +5 -2
  10. package/.out/mantine/specs/checkbox_hooks.stories.js +3 -2
  11. package/.out/mantine/specs/text_input_hooks.stories.d.ts +3 -2
  12. package/.out/mantine/specs/text_input_hooks.stories.js +3 -2
  13. package/.out/mantine/types.d.ts +3 -3
  14. package/.out/tsconfig.tsbuildinfo +1 -1
  15. package/.out/util/partial.d.ts +5 -2
  16. package/.out/util/specs/partial.tests.d.ts +1 -0
  17. package/.out/util/specs/partial.tests.js +8 -0
  18. package/.turbo/turbo-build.log +8 -8
  19. package/.turbo/turbo-check-types.log +1 -1
  20. package/.turbo/turbo-release$colon$exports.log +1 -1
  21. package/core/mobx/form_model.ts +95 -157
  22. package/core/mobx/hooks.tsx +6 -5
  23. package/core/mobx/merge_field_adapters_with_two_way_converter.ts +2 -1
  24. package/core/mobx/merge_field_adapters_with_validators.ts +1 -1
  25. package/core/mobx/specs/form_model.tests.ts +39 -27
  26. package/dist/index.cjs +93 -139
  27. package/dist/index.d.cts +28 -21
  28. package/dist/index.d.ts +28 -21
  29. package/dist/index.js +92 -139
  30. package/mantine/create_fields_view.tsx +8 -4
  31. package/mantine/hooks.tsx +23 -15
  32. package/mantine/specs/checkbox_hooks.stories.tsx +7 -1
  33. package/mantine/specs/text_input_hooks.stories.tsx +8 -1
  34. package/mantine/types.ts +12 -4
  35. package/package.json +1 -1
  36. package/util/partial.tsx +8 -1
  37. package/util/specs/partial.tests.tsx +21 -0
package/dist/index.js CHANGED
@@ -326,19 +326,27 @@ import {
326
326
  observable,
327
327
  runInAction
328
328
  } from "mobx";
329
- var _accessors_dec, _knownFields_dec, _fields_dec, _errors_dec, _fieldOverrides_dec, _value_dec, _init, _value, _fieldOverrides, _errors;
330
- _value_dec = [observable.ref], _fieldOverrides_dec = [observable.shallow], _errors_dec = [observable.shallow], _fields_dec = [computed], _knownFields_dec = [computed], _accessors_dec = [computed];
329
+ var Validation = /* @__PURE__ */ ((Validation2) => {
330
+ Validation2[Validation2["Changed"] = 1] = "Changed";
331
+ Validation2[Validation2["Always"] = 2] = "Always";
332
+ return Validation2;
333
+ })(Validation || {});
334
+ var _accessors_dec, _knownFields_dec, _fields_dec, _validation_dec, _fieldOverrides_dec, _value_dec, _init, _value, _fieldOverrides, _validation;
335
+ _value_dec = [observable.ref], _fieldOverrides_dec = [observable.shallow], _validation_dec = [observable.shallow], _fields_dec = [computed], _knownFields_dec = [computed], _accessors_dec = [computed];
331
336
  var FormModel = class {
332
337
  constructor(type, originalValue, adapters, mode) {
333
338
  this.type = type;
334
- this.originalValue = originalValue;
335
339
  this.adapters = adapters;
336
340
  this.mode = mode;
337
341
  __runInitializers(_init, 5, this);
338
342
  __privateAdd(this, _value, __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
339
343
  __privateAdd(this, _fieldOverrides, __runInitializers(_init, 12, this)), __runInitializers(_init, 15, this);
340
- __privateAdd(this, _errors, __runInitializers(_init, 16, this, {})), __runInitializers(_init, 19, this);
344
+ __privateAdd(this, _validation, __runInitializers(_init, 16, this, {})), __runInitializers(_init, 19, this);
341
345
  __publicField(this, "flattenedTypeDefs");
346
+ // cannot be type safe
347
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
348
+ __publicField(this, "originalValues");
349
+ this.originalValues = flattenValuesOfType(type, originalValue);
342
350
  this.value = mobxCopy(type, originalValue);
343
351
  this.flattenedTypeDefs = flattenTypesOfType(type);
344
352
  const conversions = flattenValueTo(
@@ -430,12 +438,14 @@ var FormModel = class {
430
438
  }
431
439
  const {
432
440
  convert,
433
- create
441
+ create,
442
+ revert
434
443
  } = adapter2;
435
444
  const fieldOverride = this.fieldOverrides[valuePath];
436
445
  const accessor = this.getAccessorForValuePath(valuePath);
437
446
  const fieldTypeDef = this.flattenedTypeDefs[typePath];
438
447
  const context = this.toContext(this.value, valuePath);
448
+ const defaultValue = create(valuePath, context);
439
449
  const {
440
450
  value,
441
451
  required,
@@ -443,14 +453,43 @@ var FormModel = class {
443
453
  } = convert(
444
454
  accessor != null ? accessor.value : fieldTypeDef != null ? mobxCopy(
445
455
  fieldTypeDef,
446
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
447
- create(valuePath, context)
448
- ) : create(valuePath, context),
456
+ defaultValue
457
+ ) : defaultValue,
449
458
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
450
459
  valuePath,
451
460
  context
452
461
  );
453
- const error = this.errors[valuePath];
462
+ let error = void 0;
463
+ const displayedValue = fieldOverride != null ? fieldOverride[0] : value;
464
+ const validation = this.validation[valuePath];
465
+ switch (validation) {
466
+ case void 0:
467
+ break;
468
+ case 1 /* Changed */:
469
+ if (revert != null) {
470
+ const originalValue = valuePath in this.originalValues ? this.originalValues[valuePath] : defaultValue;
471
+ const { value: originalDisplayedValue } = convert(originalValue, valuePath, context);
472
+ if (displayedValue !== originalDisplayedValue) {
473
+ const revertResult = revert(displayedValue, valuePath, context);
474
+ if ((revertResult == null ? void 0 : revertResult.type) === 1 /* Failure */) {
475
+ ;
476
+ ({ error } = revertResult);
477
+ }
478
+ }
479
+ }
480
+ break;
481
+ case 2 /* Always */:
482
+ {
483
+ const revertResult = revert == null ? void 0 : revert(displayedValue, valuePath, context);
484
+ if ((revertResult == null ? void 0 : revertResult.type) === 1 /* Failure */) {
485
+ ;
486
+ ({ error } = revertResult);
487
+ }
488
+ }
489
+ break;
490
+ default:
491
+ throw new UnreachableError2(validation);
492
+ }
454
493
  return {
455
494
  value: fieldOverride != null ? fieldOverride[0] : value,
456
495
  error,
@@ -485,11 +524,8 @@ var FormModel = class {
485
524
  typePath(valuePath) {
486
525
  return valuePathToTypePath(this.type, valuePath, true);
487
526
  }
488
- setFieldValueAndValidate(valuePath, value) {
489
- return this.internalSetFieldValue(valuePath, value, true);
490
- }
491
- setFieldValue(valuePath, value) {
492
- return this.internalSetFieldValue(valuePath, value, false);
527
+ setFieldValue(valuePath, value, validation = this.validation[valuePath]) {
528
+ return this.internalSetFieldValue(valuePath, value, validation);
493
529
  }
494
530
  addListItem(valuePath, elementValue = null, index) {
495
531
  const listValuePath = valuePath;
@@ -548,9 +584,9 @@ var FormModel = class {
548
584
  const fieldOverride = this.fieldOverrides[fromJsonPath];
549
585
  delete this.fieldOverrides[fromJsonPath];
550
586
  this.fieldOverrides[toJsonPath] = fieldOverride;
551
- const error = this.errors[fromJsonPath];
552
- delete this.errors[fromJsonPath];
553
- this.errors[toJsonPath] = error;
587
+ const validation = this.validation[fromJsonPath];
588
+ delete this.validation[fromJsonPath];
589
+ this.validation[toJsonPath] = validation;
554
590
  });
555
591
  accessor.set(newList);
556
592
  delete this.fieldOverrides[listValuePath];
@@ -614,33 +650,34 @@ var FormModel = class {
614
650
  const fieldOverride = this.fieldOverrides[fromJsonPath];
615
651
  delete this.fieldOverrides[fromJsonPath];
616
652
  this.fieldOverrides[toJsonPath] = fieldOverride;
617
- const error = this.errors[fromJsonPath];
618
- delete this.errors[fromJsonPath];
619
- this.errors[toJsonPath] = error;
653
+ const validation = this.validation[fromJsonPath];
654
+ delete this.validation[fromJsonPath];
655
+ this.validation[toJsonPath] = validation;
620
656
  });
621
657
  accessor.set(newList);
622
658
  delete this.fieldOverrides[listValuePath];
623
659
  });
624
660
  });
625
661
  }
626
- internalSetFieldValue(valuePath, value, displayValidation) {
662
+ internalSetFieldValue(valuePath, value, validation) {
627
663
  const { revert } = this.getAdapterForValuePath(valuePath);
628
664
  assertExists(revert, "setting value not supported {}", valuePath);
629
665
  const conversion = revert(value, valuePath, this.toContext(this.value, valuePath));
630
666
  const accessor = this.getAccessorForValuePath(valuePath);
631
667
  return runInAction(() => {
632
668
  this.fieldOverrides[valuePath] = [value];
669
+ if (validation != null) {
670
+ this.validation[valuePath] = validation;
671
+ } else {
672
+ delete this.validation[valuePath];
673
+ }
633
674
  switch (conversion.type) {
634
675
  case 1 /* Failure */:
635
- if (displayValidation) {
636
- this.errors[valuePath] = conversion.error;
637
- }
638
676
  if (conversion.value != null && accessor != null) {
639
677
  accessor.set(conversion.value[0]);
640
678
  }
641
679
  return false;
642
680
  case 0 /* Success */:
643
- delete this.errors[valuePath];
644
681
  accessor == null ? void 0 : accessor.set(conversion.value);
645
682
  return true;
646
683
  default:
@@ -652,7 +689,7 @@ var FormModel = class {
652
689
  const fieldOverride = this.fieldOverrides[valuePath];
653
690
  if (fieldOverride != null) {
654
691
  runInAction(() => {
655
- delete this.errors[valuePath];
692
+ delete this.validation[valuePath];
656
693
  });
657
694
  }
658
695
  }
@@ -674,11 +711,12 @@ var FormModel = class {
674
711
  const key = valuePath;
675
712
  runInAction(() => {
676
713
  this.fieldOverrides[key] = [displayValue];
714
+ delete this.validation[key];
677
715
  });
678
716
  }
679
717
  clearAll(value) {
680
718
  runInAction(() => {
681
- this.errors = {};
719
+ this.validation = {};
682
720
  this.fieldOverrides = {};
683
721
  this.value = mobxCopy(this.type, value);
684
722
  });
@@ -688,123 +726,37 @@ var FormModel = class {
688
726
  const keys = new Set(Object.keys(values));
689
727
  return keys.has(valuePath);
690
728
  }
691
- validateField(valuePath, ignoreDefaultValue = false) {
692
- const {
693
- convert,
694
- revert,
695
- create
696
- } = this.getAdapterForValuePath(valuePath);
697
- const fieldOverride = this.fieldOverrides[valuePath];
698
- const accessor = this.getAccessorForValuePath(valuePath);
699
- const context = this.toContext(this.value, valuePath);
700
- const {
701
- value: storedValue
702
- } = convert(
703
- accessor != null ? accessor.value : create(valuePath, context),
704
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
705
- valuePath,
706
- context
707
- );
708
- const value = fieldOverride != null ? fieldOverride[0] : storedValue;
709
- const dirty = storedValue !== value;
710
- assertExists(revert, "changing field directly not supported {}", valuePath);
711
- if (ignoreDefaultValue) {
712
- const {
713
- value: defaultDisplayValue
714
- } = convert(create(valuePath, context), valuePath, context);
715
- if (defaultDisplayValue === value) {
716
- return true;
717
- }
718
- }
719
- const conversion = revert(value, valuePath, context);
720
- return runInAction(() => {
721
- switch (conversion.type) {
722
- case 1 /* Failure */:
723
- this.errors[valuePath] = conversion.error;
724
- if (conversion.value != null && accessor != null && dirty) {
725
- accessor.set(conversion.value[0]);
726
- }
727
- return false;
728
- case 0 /* Success */:
729
- delete this.errors[valuePath];
730
- if (accessor != null && dirty) {
731
- accessor.set(conversion.value);
732
- }
733
- return true;
734
- default:
735
- throw new UnreachableError2(conversion);
736
- }
729
+ validateField(valuePath, validation = Math.max(
730
+ this.mode === "create" ? 2 /* Always */ : 1 /* Changed */,
731
+ ((_a) => (_a = this.validation[valuePath]) != null ? _a : 1 /* Changed */)()
732
+ )) {
733
+ runInAction(() => {
734
+ this.validation[valuePath] = validation;
737
735
  });
736
+ return this.fields[valuePath].error == null;
738
737
  }
739
- validateAll(force = this.mode === "create") {
740
- const accessors = toArray(this.accessors).toSorted(function([a], [b]) {
741
- return a.length - b.length;
742
- });
743
- const flattenedOriginalValues = flattenValuesOfType(this.type, this.originalValue);
744
- return runInAction(() => {
745
- return accessors.reduce(
746
- (success, [
747
- valuePath,
748
- accessor
749
- ]) => {
750
- const adapterPath = valuePath;
751
- const adapter2 = this.maybeGetAdapterForValuePath(adapterPath);
752
- if (adapter2 == null) {
753
- return success;
754
- }
755
- const {
756
- convert,
757
- revert
758
- } = adapter2;
759
- if (revert == null) {
760
- return success;
761
- }
762
- const fieldOverride = this.fieldOverrides[adapterPath];
763
- const context = this.toContext(this.value, valuePath);
764
- const {
765
- value: storedValue
766
- } = convert(accessor.value, valuePath, context);
767
- const value = fieldOverride != null ? fieldOverride[0] : storedValue;
768
- const dirty = fieldOverride != null && fieldOverride[0] !== storedValue;
769
- const needsValidation = force || !(valuePath in flattenedOriginalValues) || storedValue !== convert(
770
- flattenedOriginalValues[valuePath],
771
- valuePath,
772
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
773
- this.toContext(this.originalValue, valuePath)
774
- ).value;
775
- if (needsValidation) {
776
- const conversion = revert(value, valuePath, context);
777
- switch (conversion.type) {
778
- case 1 /* Failure */:
779
- this.errors[adapterPath] = conversion.error;
780
- if (conversion.value != null && dirty) {
781
- accessor.set(conversion.value[0]);
782
- }
783
- return false;
784
- case 0 /* Success */:
785
- if (dirty) {
786
- accessor.set(conversion.value);
787
- }
788
- delete this.errors[adapterPath];
789
- return success;
790
- default:
791
- throw new UnreachableError2(conversion);
792
- }
793
- }
794
- return success;
795
- },
796
- true
797
- );
738
+ validateAll(validation = this.mode === "create" ? 2 /* Always */ : 1 /* Changed */) {
739
+ const accessors = toArray(this.accessors);
740
+ runInAction(() => {
741
+ accessors.forEach(([valuePath]) => {
742
+ this.validation[valuePath] = validation;
743
+ });
798
744
  });
745
+ return accessors.every(
746
+ ([valuePath]) => {
747
+ const field = this.fields[valuePath];
748
+ return (field == null ? void 0 : field.error) == null;
749
+ }
750
+ );
799
751
  }
800
752
  };
801
753
  _init = __decoratorStart(null);
802
754
  _value = new WeakMap();
803
755
  _fieldOverrides = new WeakMap();
804
- _errors = new WeakMap();
756
+ _validation = new WeakMap();
805
757
  __decorateElement(_init, 4, "value", _value_dec, FormModel, _value);
806
758
  __decorateElement(_init, 4, "fieldOverrides", _fieldOverrides_dec, FormModel, _fieldOverrides);
807
- __decorateElement(_init, 4, "errors", _errors_dec, FormModel, _errors);
759
+ __decorateElement(_init, 4, "validation", _validation_dec, FormModel, _validation);
808
760
  __decorateElement(_init, 2, "fields", _fields_dec, FormModel);
809
761
  __decorateElement(_init, 2, "knownFields", _knownFields_dec, FormModel);
810
762
  __decorateElement(_init, 2, "accessors", _accessors_dec, FormModel);
@@ -820,8 +772,7 @@ function useDefaultMobxFormHooks(model, {
820
772
  } = {}) {
821
773
  const onFieldValueChange = useCallback(
822
774
  function(path, value) {
823
- model.clearFieldError(path);
824
- model.setFieldValue(path, value);
775
+ model.setFieldValue(path, value, null);
825
776
  },
826
777
  [model]
827
778
  );
@@ -841,7 +792,7 @@ function useDefaultMobxFormHooks(model, {
841
792
  function(path) {
842
793
  setTimeout(function() {
843
794
  if (model.isValuePathActive(path)) {
844
- model.validateField(path, true);
795
+ model.validateField(path);
845
796
  }
846
797
  }, 100);
847
798
  },
@@ -933,10 +884,11 @@ function mergeAdaptersWithValidators(adapters, validators) {
933
884
  readonly: readonly1 || readonly2
934
885
  };
935
886
  }
936
- acc[key] = __spreadProps(__spreadValues({}, adapter2), {
887
+ acc[key] = {
888
+ create: adapter2.create.bind(adapter2),
937
889
  convert,
938
890
  revert: adapter2.revert && revert
939
- });
891
+ };
940
892
  return acc;
941
893
  },
942
894
  {}
@@ -1997,6 +1949,7 @@ export {
1997
1949
  SelectStringConverter,
1998
1950
  TrimmingStringConverter,
1999
1951
  UnreliableFieldConversionType,
1952
+ Validation,
2000
1953
  adapter,
2001
1954
  adapterFromPrototype,
2002
1955
  adapterFromTwoWayConverter,
@@ -15,14 +15,18 @@ import type { SubFormFields } from 'types/sub_form_fields'
15
15
  import type { ValueTypeOfField } from 'types/value_type_of_field'
16
16
  import type { MantineFieldComponent } from './types'
17
17
 
18
+ export type SubPathsOf<ValuePath extends string, SubFormValuePath extends string> = SubFormValuePath extends
19
+ StringConcatOf<ValuePath, infer Postfix> ? `$${Postfix}`
20
+ : never
21
+
18
22
  export type CallbackMapper<ValuePath extends string> = {
19
23
  <
20
24
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
25
  Cb extends (...args: any[]) => any,
22
- >(cb: Cb): Parameters<Cb> extends [infer SubFormValuePath extends string, ...(infer Rest)]
23
- ? SubFormValuePath extends StringConcatOf<ValuePath, infer Postfix>
24
- ? (valuePath: `$${Postfix}`, ...rest: Rest) => ReturnType<Cb>
25
- : never
26
+ >(cb: Cb): Parameters<Cb> extends [infer SubFormValuePath extends string, ...(infer Rest)] ? (
27
+ valuePath: SubPathsOf<ValuePath, SubFormValuePath>,
28
+ ...rest: Rest
29
+ ) => ReturnType<Cb>
26
30
  : never,
27
31
  }
28
32
 
package/mantine/hooks.tsx CHANGED
@@ -40,6 +40,7 @@ import { type ListFieldsOfFields } from 'types/list_fields_of_fields'
40
40
  import { type StringFieldsOfFields } from 'types/string_fields_of_fields'
41
41
  import { type SubFormFields } from 'types/sub_form_fields'
42
42
  import { type ValueTypeOfField } from 'types/value_type_of_field'
43
+ import { type RefOfProps } from 'util/partial'
43
44
  import {
44
45
  createCheckbox,
45
46
  type SuppliedCheckboxProps,
@@ -154,11 +155,11 @@ class MantineFormImpl<
154
155
  private readonly valueInputCache: Cache<
155
156
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
157
  [keyof AllFieldsOfFields<F>, ComponentType<SuppliedValueInputProps<any>>],
157
- MantineFieldComponent<SuppliedTextInputProps>
158
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
159
+ MantineFieldComponent<SuppliedValueInputProps<any>>
158
160
  > = new Cache(
159
161
  createValueInput.bind(this),
160
162
  )
161
-
162
163
  private readonly checkboxCache: Cache<
163
164
  [keyof BooleanFieldsOfFields<F>, ComponentType<SuppliedCheckboxProps>],
164
165
  MantineFieldComponent<SuppliedCheckboxProps>
@@ -223,7 +224,7 @@ class MantineFormImpl<
223
224
 
224
225
  textInput<
225
226
  K extends keyof StringFieldsOfFields<F>,
226
- >(valuePath: K): MantineFieldComponent<SuppliedTextInputProps, TextInputProps, ErrorOfField<F[K]>>
227
+ >(valuePath: K): MantineFieldComponent<SuppliedTextInputProps, TextInputProps, ErrorOfField<F[K]>, HTMLInputElement>
227
228
  textInput<
228
229
  K extends keyof StringFieldsOfFields<F>,
229
230
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -231,7 +232,7 @@ class MantineFormImpl<
231
232
  >(
232
233
  valuePath: K,
233
234
  TextInput?: ComponentType<P>,
234
- ): MantineFieldComponent<SuppliedTextInputProps, P, ErrorOfField<F[K]>>
235
+ ): MantineFieldComponent<SuppliedTextInputProps, P, ErrorOfField<F[K]>, RefOfProps<P, HTMLInputElement>>
235
236
  textInput<
236
237
  K extends keyof StringFieldsOfFields<F>,
237
238
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -240,13 +241,14 @@ class MantineFormImpl<
240
241
  valuePath: K,
241
242
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
242
243
  TextInput: ComponentType<P> = TextInputImpl as ComponentType<P>,
243
- ): MantineFieldComponent<SuppliedTextInputProps, P, ErrorOfField<F[K]>> {
244
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
245
+ ): MantineFieldComponent<SuppliedTextInputProps, P, ErrorOfField<F[K]>, any> {
244
246
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
245
247
  return this.textInputCache.retrieveOrCreate(
246
248
  valuePath,
247
249
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
248
250
  TextInput as ComponentType<SuppliedTextInputProps>,
249
- ) as MantineFieldComponent<SuppliedTextInputProps, P, ErrorOfField<F[K]>>
251
+ ) as unknown as MantineFieldComponent<SuppliedTextInputProps, P, ErrorOfField<F[K]>>
250
252
  }
251
253
 
252
254
  valueInput<
@@ -262,7 +264,7 @@ class MantineFormImpl<
262
264
  valuePath,
263
265
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
264
266
  ValueInput as ComponentType<SuppliedValueInputProps<ValueTypeOfField<F[K]>>>,
265
- ) as MantineFieldComponent<SuppliedTextInputProps, P, ErrorOfField<F[K]>>
267
+ ) as unknown as MantineFieldComponent<SuppliedValueInputProps<ValueTypeOfField<F[K]>>, P, ErrorOfField<F[K]>>
266
268
  }
267
269
 
268
270
  select<
@@ -273,30 +275,36 @@ class MantineFormImpl<
273
275
  valuePath,
274
276
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
275
277
  SimpleSelect as ComponentType<SuppliedValueInputProps<ValueTypeOfField<F[K]>>>,
276
- ) as MantineFieldComponent<SuppliedTextInputProps, ComponentProps<typeof SimpleSelect>, ErrorOfField<F[K]>>
278
+ ) as unknown as MantineFieldComponent<
279
+ SuppliedValueInputProps<ValueTypeOfField<F[K]>>,
280
+ ComponentProps<typeof SimpleSelect>,
281
+ ErrorOfField<F[K]>,
282
+ HTMLSelectElement
283
+ >
277
284
  }
278
285
 
279
286
  checkbox<
280
287
  K extends keyof BooleanFieldsOfFields<F>,
281
- >(valuePath: K): MantineFieldComponent<SuppliedCheckboxProps, CheckboxProps, ErrorOfField<F[K]>>
288
+ >(valuePath: K): MantineFieldComponent<SuppliedCheckboxProps, CheckboxProps, ErrorOfField<F[K]>, HTMLInputElement>
282
289
  checkbox<
283
290
  K extends keyof BooleanFieldsOfFields<F>,
284
291
  P extends SuppliedCheckboxProps,
285
292
  >(
286
293
  valuePath: K,
287
294
  Checkbox: ComponentType<P>,
288
- ): MantineFieldComponent<SuppliedCheckboxProps, P, ErrorOfField<F[K]>>
295
+ ): MantineFieldComponent<SuppliedCheckboxProps, P, ErrorOfField<F[K]>, RefOfProps<P, HTMLInputElement>>
289
296
  checkbox<K extends keyof BooleanFieldsOfFields<F>, P extends SuppliedCheckboxProps>(
290
297
  valuePath: K,
291
298
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
292
299
  Checkbox: ComponentType<P> = CheckboxImpl as ComponentType<P>,
293
- ): MantineFieldComponent<SuppliedCheckboxProps, P, ErrorOfField<F[K]>> {
300
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
301
+ ): MantineFieldComponent<SuppliedCheckboxProps, P, ErrorOfField<F[K]>, any> {
294
302
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
295
303
  return this.checkboxCache.retrieveOrCreate(
296
304
  valuePath,
297
305
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
298
306
  Checkbox as ComponentType<SuppliedCheckboxProps>,
299
- ) as MantineFieldComponent<SuppliedCheckboxProps, P, ErrorOfField<F[K]>>
307
+ ) as unknown as MantineFieldComponent<SuppliedCheckboxProps, P, ErrorOfField<F[K]>>
300
308
  }
301
309
 
302
310
  // this should work?
@@ -324,7 +332,7 @@ class MantineFormImpl<
324
332
  valuePath,
325
333
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
326
334
  RadioGroup as ComponentType<SuppliedRadioGroupProps>,
327
- ) as MantineFieldComponent<SuppliedRadioGroupProps, P, ErrorOfField<F[K]>>
335
+ ) as unknown as MantineFieldComponent<SuppliedRadioGroupProps, P, ErrorOfField<F[K]>>
328
336
  }
329
337
 
330
338
  radio<
@@ -356,7 +364,7 @@ class MantineFormImpl<
356
364
  value,
357
365
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
358
366
  Radio as ComponentType<SuppliedRadioProps>,
359
- ) as MantineFieldComponent<SuppliedRadioProps, P, ErrorOfField<F[K]>>
367
+ ) as unknown as MantineFieldComponent<SuppliedRadioProps, P, ErrorOfField<F[K]>>
360
368
  }
361
369
 
362
370
  pill<
@@ -382,7 +390,7 @@ class MantineFormImpl<
382
390
  valuePath,
383
391
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
384
392
  Pill as ComponentType<SuppliedPillProps>,
385
- ) as MantineFieldComponent<SuppliedPillProps, P, ErrorOfField<F[K]>>
393
+ ) as unknown as MantineFieldComponent<SuppliedPillProps, P, ErrorOfField<F[K]>>
386
394
  }
387
395
 
388
396
  list<
@@ -5,6 +5,7 @@ import {
5
5
  } from '@storybook/react'
6
6
  import { type FieldsViewProps } from 'core/props'
7
7
  import { useMantineFormFields } from 'mantine/hooks'
8
+ import { type Ref } from 'react'
8
9
  import { type Field } from 'types/field'
9
10
  import { CHECKBOX_LABEL } from './checkbox_constants'
10
11
 
@@ -13,16 +14,20 @@ function ErrorRenderer({ error }: { error: string }) {
13
14
  }
14
15
 
15
16
  function Component({
17
+ componentRef,
16
18
  ...props
17
19
  }: FieldsViewProps<{
18
20
  $: Field<boolean, string>,
19
- }>) {
21
+ }> & {
22
+ componentRef: Ref<HTMLInputElement>,
23
+ }) {
20
24
  const inputProps = useMantineFormFields(props)
21
25
  const CheckboxComponent = inputProps.checkbox('$')
22
26
  return (
23
27
  <CheckboxComponent
24
28
  ErrorRenderer={ErrorRenderer}
25
29
  label={CHECKBOX_LABEL}
30
+ ref={componentRef}
26
31
  />
27
32
  )
28
33
  }
@@ -34,6 +39,7 @@ const meta: Meta<typeof Component> = {
34
39
  onFieldFocus: action('onFieldFocus'),
35
40
  onFieldSubmit: action('onFieldSubmit'),
36
41
  onFieldValueChange: action('onFieldValueChange'),
42
+ componentRef: action('componentRef'),
37
43
  },
38
44
  }
39
45
 
@@ -14,7 +14,10 @@ import {
14
14
  type TextInputTarget,
15
15
  } from 'mantine/create_text_input'
16
16
  import { useMantineFormFields } from 'mantine/hooks'
17
- import { type ComponentType } from 'react'
17
+ import {
18
+ type ComponentType,
19
+ type Ref,
20
+ } from 'react'
18
21
  import { type Field } from 'types/field'
19
22
  import { TEXT_INPUT_LABEL } from './text_input_constants'
20
23
 
@@ -26,10 +29,12 @@ function ErrorRenderer({ error }: { error: string }) {
26
29
 
27
30
  function Component<T extends TextInputTarget>({
28
31
  TextInput,
32
+ componentRef,
29
33
  ...props
30
34
  }: FieldsViewProps<{
31
35
  $: Field<string, string>,
32
36
  }> & {
37
+ componentRef: Ref<HTMLInputElement>,
33
38
  TextInput?: ComponentType<StoryTextInputProps<T>>,
34
39
  }) {
35
40
  const form = useMantineFormFields(props)
@@ -38,6 +43,7 @@ function Component<T extends TextInputTarget>({
38
43
  <TextInputComponent
39
44
  ErrorRenderer={ErrorRenderer}
40
45
  label={TEXT_INPUT_LABEL}
46
+ ref={componentRef}
41
47
  />
42
48
  )
43
49
  }
@@ -49,6 +55,7 @@ const meta: Meta<typeof Component> = {
49
55
  onFieldFocus: action('onFieldFocus'),
50
56
  onFieldSubmit: action('onFieldSubmit'),
51
57
  onFieldValueChange: action('onFieldValueChange'),
58
+ componentRef: action('componentRef'),
52
59
  },
53
60
  }
54
61
 
package/mantine/types.ts CHANGED
@@ -1,6 +1,11 @@
1
- import { type ComponentType } from 'react'
1
+ import {
2
+ type ComponentType,
3
+ } from 'react'
2
4
  import { type Fields } from 'types/field'
3
- import { type UnsafePartialComponent } from 'util/partial'
5
+ import {
6
+ type RefOfProps,
7
+ type UnsafePartialComponent,
8
+ } from 'util/partial'
4
9
  import { type ErrorRenderer } from './error_renderer'
5
10
 
6
11
  export type MantineForm<F extends Fields> = {
@@ -12,10 +17,13 @@ export type MantineForm<F extends Fields> = {
12
17
  }
13
18
 
14
19
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
- export type MantineFieldComponent<T, P = T, E = any> = UnsafePartialComponent<
20
+ export type MantineFieldComponent<T, P = T, E = any, R = RefOfProps<P>> = UnsafePartialComponent<
16
21
  ComponentType<P>,
17
22
  T,
18
23
  // escape hatch for never comparisons `E extends never` will not work, always returning never
19
24
  // https://github.com/microsoft/TypeScript/issues/31751
20
- [E] extends [never] ? {} : { ErrorRenderer: ErrorRenderer<E> }
25
+ [E] extends [never] ? {} : { ErrorRenderer: ErrorRenderer<E> },
26
+ // mantine types are too complex for us to be able to get a stable type for the ref.
27
+ // We can, however, do a best guess and allow overriding in the caller
28
+ R
21
29
  >
package/package.json CHANGED
@@ -70,7 +70,7 @@
70
70
  "test:watch": "vitest"
71
71
  },
72
72
  "type": "module",
73
- "version": "0.0.18",
73
+ "version": "0.0.19",
74
74
  "exports": {
75
75
  ".": {
76
76
  "import": {
package/util/partial.tsx CHANGED
@@ -8,9 +8,13 @@ import {
8
8
  forwardRef,
9
9
  type ForwardRefExoticComponent,
10
10
  type PropsWithoutRef,
11
+ type Ref,
12
+ type RefAttributes,
11
13
  useMemo,
12
14
  } from 'react'
13
15
 
16
+ export type RefOfProps<P, Fallback = unknown> = P extends RefAttributes<infer R> ? R : Fallback
17
+
14
18
  export type PartialComponent<
15
19
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
20
  Component extends ComponentType<any>,
@@ -27,8 +31,11 @@ export type UnsafePartialComponent<
27
31
  Component extends ComponentType<any>,
28
32
  CurriedProps,
29
33
  AdditionalProps = {},
34
+ R = RefOfProps<ComponentProps<Component>>,
30
35
  > = ForwardRefExoticComponent<
31
- PropsWithoutRef<RemainingComponentProps<Component, CurriedProps> & AdditionalProps>
36
+ PropsWithoutRef<RemainingComponentProps<Component, CurriedProps> & AdditionalProps> & {
37
+ ref?: Ref<R>,
38
+ }
32
39
  >
33
40
 
34
41
  export function createSimplePartialComponent<