@strictly/react-form 0.0.8 → 0.0.10

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 (44) hide show
  1. package/.out/core/mobx/field_adapter_builder.d.ts +4 -0
  2. package/.out/core/mobx/field_adapter_builder.js +31 -0
  3. package/.out/core/mobx/form_presenter.d.ts +5 -5
  4. package/.out/core/mobx/form_presenter.js +8 -6
  5. package/.out/core/mobx/hooks.d.ts +24 -4
  6. package/.out/core/mobx/hooks.js +24 -3
  7. package/.out/core/mobx/specs/form_presenter.tests.js +10 -5
  8. package/.out/core/mobx/sub_form_field_adapters.d.ts +2 -2
  9. package/.out/field_converters/chain_field_converter.js +3 -3
  10. package/.out/mantine/create_fields_view.d.ts +9 -1
  11. package/.out/mantine/create_fields_view.js +13 -1
  12. package/.out/mantine/error_renderer.d.ts +7 -3
  13. package/.out/mantine/hooks.d.ts +2 -1
  14. package/.out/mantine/hooks.js +1 -1
  15. package/.out/mantine/specs/create_fields_view.tests.js +17 -0
  16. package/.out/mantine/specs/fields_view_hooks.stories.d.ts +6 -2
  17. package/.out/mantine/specs/fields_view_hooks.stories.js +26 -7
  18. package/.out/mantine/specs/fields_view_hooks.tests.js +21 -1
  19. package/.out/tsconfig.tsbuildinfo +1 -1
  20. package/.out/types/specs/error_of_field.tests.d.ts +1 -0
  21. package/.out/types/specs/{error_type_of_field.tests.js → error_of_field.tests.js} +1 -1
  22. package/.turbo/turbo-build.log +8 -8
  23. package/.turbo/turbo-check-types.log +1 -1
  24. package/core/mobx/field_adapter_builder.ts +71 -0
  25. package/core/mobx/form_presenter.ts +15 -14
  26. package/core/mobx/hooks.tsx +196 -0
  27. package/core/mobx/specs/form_presenter.tests.ts +24 -5
  28. package/core/mobx/sub_form_field_adapters.ts +14 -3
  29. package/dist/index.cjs +290 -220
  30. package/dist/index.d.cts +63 -32
  31. package/dist/index.d.ts +63 -32
  32. package/dist/index.js +288 -219
  33. package/field_converters/chain_field_converter.ts +3 -3
  34. package/mantine/create_fields_view.tsx +66 -31
  35. package/mantine/error_renderer.ts +12 -3
  36. package/mantine/hooks.tsx +9 -6
  37. package/mantine/specs/__snapshots__/fields_view_hooks.tests.tsx.snap +194 -197
  38. package/mantine/specs/create_fields_view.tests.ts +29 -0
  39. package/mantine/specs/fields_view_hooks.stories.tsx +58 -15
  40. package/mantine/specs/fields_view_hooks.tests.tsx +26 -0
  41. package/package.json +1 -1
  42. package/types/specs/{error_type_of_field.tests.ts → error_of_field.tests.ts} +1 -1
  43. package/core/mobx/hooks.ts +0 -112
  44. /package/.out/{types/specs/error_type_of_field.tests.d.ts → mantine/specs/create_fields_view.tests.d.ts} +0 -0
@@ -7,6 +7,9 @@ declare class FieldAdapterBuilder<From, To, E, ValuePath extends string, Context
7
7
  constructor(convert: AnnotatedFieldConverter<From, To, ValuePath, Context>, create: FieldValueFactory<From, ValuePath, Context>, revert?: UnreliableFieldConverter<To, From, E, ValuePath, Context> | undefined);
8
8
  chain<To2, E2 = E>(converter: AnnotatedFieldConverter<To, To2, ValuePath, Context>, reverter?: UnreliableFieldConverter<To2, To, E2, ValuePath, Context>): FieldAdapterBuilder<From, To2, E | E2, ValuePath, Context>;
9
9
  withReverter(reverter: UnreliableFieldConverter<To, From, E, ValuePath, Context>): FieldAdapterBuilder<From, To, E, ValuePath, Context>;
10
+ nullable(): FieldAdapterBuilder<From | null, To | null, E, ValuePath, Context>;
11
+ optional(): FieldAdapterBuilder<From | undefined, To | undefined, E, ValuePath, Context>;
12
+ private or;
10
13
  withIdentity(isFrom: (from: To | From) => from is From): FieldAdapterBuilder<From, To | From, E, ValuePath, Context>;
11
14
  get narrow(): FieldAdapter<From, To, E, ValuePath, Context>;
12
15
  }
@@ -17,5 +20,6 @@ export declare function adapterFromTwoWayConverter<From, To, E, ValuePath extend
17
20
  export declare function adapterFromPrototype<From, To, ValuePath extends string, Context>(converter: AnnotatedFieldConverter<From, To, ValuePath, Context>, prototype: From): FieldAdapterBuilder<From, To, never, ValuePath, Context>;
18
21
  export declare function adapterFromPrototype<From, To, E, ValuePath extends string, Context>(converter: TwoWayFieldConverter<From, To, E, ValuePath, Context>, prototype: From): FieldAdapterBuilder<From, To, E, ValuePath, Context>;
19
22
  export declare function identityAdapter<V, ValuePath extends string, Context>(prototype: V, required?: boolean): FieldAdapterBuilder<V, V, never, ValuePath, Context>;
23
+ export declare function trimmingStringAdapter<ValuePath extends string, Context>(): FieldAdapterBuilder<string, string, never, ValuePath, Context>;
20
24
  export declare function listAdapter<E, ValuePath extends string, Context>(): FieldAdapterBuilder<readonly E[], readonly E[], never, ValuePath, Context>;
21
25
  export {};
@@ -1,7 +1,9 @@
1
1
  import { chainAnnotatedFieldConverter, chainUnreliableFieldConverter, } from 'field_converters/chain_field_converter';
2
2
  import { annotatedIdentityConverter, unreliableIdentityConverter, } from 'field_converters/identity_converter';
3
3
  import { MaybeIdentityConverter } from 'field_converters/maybe_identity_converter';
4
+ import { TrimmingStringConverter } from 'field_converters/trimming_string_converter';
4
5
  import { prototypingFieldValueFactory } from 'field_value_factories/prototyping_field_value_factory';
6
+ import { UnreliableFieldConversionType, } from 'types/field_converters';
5
7
  class FieldAdapterBuilder {
6
8
  convert;
7
9
  create;
@@ -17,6 +19,32 @@ class FieldAdapterBuilder {
17
19
  withReverter(reverter) {
18
20
  return new FieldAdapterBuilder(this.convert, this.create, reverter);
19
21
  }
22
+ nullable() {
23
+ return this.or(null);
24
+ }
25
+ optional() {
26
+ return this.or(undefined);
27
+ }
28
+ or(proto) {
29
+ function isFrom(v) {
30
+ return v !== proto;
31
+ }
32
+ function isTo(v) {
33
+ return v !== proto;
34
+ }
35
+ return new FieldAdapterBuilder((v, valuePath, context) => isFrom(v)
36
+ ? this.convert(v, valuePath, context)
37
+ : {
38
+ value: v,
39
+ readonly: false,
40
+ required: false,
41
+ }, this.create, (v, valuePath, context) => isTo(v) && this.revert
42
+ ? this.revert(v, valuePath, context)
43
+ : {
44
+ type: UnreliableFieldConversionType.Success,
45
+ value: proto,
46
+ });
47
+ }
20
48
  withIdentity(isFrom) {
21
49
  const identityConverter = new MaybeIdentityConverter({
22
50
  convert: this.convert,
@@ -46,6 +74,9 @@ export function adapterFromPrototype(converter, prototype) {
46
74
  export function identityAdapter(prototype, required) {
47
75
  return new FieldAdapterBuilder(annotatedIdentityConverter(required), prototypingFieldValueFactory(prototype), unreliableIdentityConverter());
48
76
  }
77
+ export function trimmingStringAdapter() {
78
+ return adapterFromTwoWayConverter(new TrimmingStringConverter(), prototypingFieldValueFactory(''));
79
+ }
49
80
  export function listAdapter() {
50
81
  return new FieldAdapterBuilder(annotatedIdentityConverter(false), prototypingFieldValueFactory([]), unreliableIdentityConverter());
51
82
  }
@@ -20,9 +20,9 @@ type FlattenedErrors<ValuePathsToAdapters extends Readonly<Record<string, FieldA
20
20
  export type ValuePathsToAdaptersOf<TypePathsToAdapters extends Partial<Readonly<Record<string, FieldAdapter>>>, ValuePathsToTypePaths extends Readonly<Record<string, string>>> = keyof TypePathsToAdapters extends ValueOf<ValuePathsToTypePaths> ? {
21
21
  readonly [K in keyof ValuePathsToTypePaths as unknown extends TypePathsToAdapters[ValuePathsToTypePaths[K]] ? never : K]: NonNullable<TypePathsToAdapters[ValuePathsToTypePaths[K]]>;
22
22
  } : never;
23
- export declare class FormPresenter<T extends Type, ValueToTypePaths extends Readonly<Record<string, string>>, TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<FlattenedValuesOfType<T, '*'>, ValueOfType<ReadonlyTypeOfType<T>>>, ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths>> {
23
+ export declare abstract class FormPresenter<T extends Type, ValueToTypePaths extends Readonly<Record<string, string>>, TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<FlattenedValuesOfType<T, '*'>, ValueOfType<ReadonlyTypeOfType<T>>>, ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths>> {
24
24
  readonly type: T;
25
- private readonly adapters;
25
+ protected readonly adapters: TypePathsToAdapters;
26
26
  constructor(type: T, adapters: TypePathsToAdapters);
27
27
  private maybeGetAdapterForValuePath;
28
28
  private getAdapterForValuePath;
@@ -33,12 +33,12 @@ export declare class FormPresenter<T extends Type, ValueToTypePaths extends Read
33
33
  removeListItem<K extends keyof FlattenedListTypesOfType<T>>(model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>, elementValuePath: `${K}.${number}`): void;
34
34
  private internalSetFieldValue;
35
35
  clearFieldError<K extends keyof ValuePathsToAdapters>(model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>, valuePath: K): void;
36
- clearFieldValue<K extends StringKeyOf<ValuePathsToAdapters>>(model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>, valuePath: K): void;
36
+ clearFieldValue<K extends StringKeyOf<ValueToTypePaths>>(model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>, valuePath: K): void;
37
37
  clearAll(model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>, value: ValueOfType<T>): void;
38
38
  isValuePathActive<K extends keyof ValuePathsToAdapters>(model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>, valuePath: K): boolean;
39
- validateField<K extends keyof ValuePathsToAdapters>(model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>, valuePath: K): boolean;
39
+ validateField<K extends keyof ValuePathsToAdapters>(model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>, valuePath: K, ignoreDefaultValue?: boolean): boolean;
40
40
  validateAll(model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>): boolean;
41
- createModel(value: ValueOfType<ReadonlyTypeOfType<T>>): FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>;
41
+ abstract createModel(value: ValueOfType<ReadonlyTypeOfType<T>>): FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>;
42
42
  }
43
43
  export declare class FormModel<T extends Type, ValueToTypePaths extends Readonly<Record<string, string>>, TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<FlattenedValuesOfType<T, '*'>, ValueOfType<ReadonlyTypeOfType<T>>>, ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths>> {
44
44
  private readonly type;
@@ -188,8 +188,7 @@ export class FormPresenter {
188
188
  return;
189
189
  }
190
190
  const { convert, create, } = adapter;
191
- const accessor = model.accessors[valuePath];
192
- const value = accessor == null ? create(valuePath, model.value) : accessor.value;
191
+ const value = create(valuePath, model.value);
193
192
  const { value: displayValue, } = convert(value, valuePath, model.value);
194
193
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
195
194
  const key = valuePath;
@@ -211,7 +210,7 @@ export class FormPresenter {
211
210
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
212
211
  return keys.has(valuePath);
213
212
  }
214
- validateField(model, valuePath) {
213
+ validateField(model, valuePath, ignoreDefaultValue = false) {
215
214
  const { convert, revert, create, } = this.getAdapterForValuePath(valuePath);
216
215
  const fieldOverride = model.fieldOverrides[valuePath];
217
216
  const accessor = model.getAccessorForValuePath(valuePath);
@@ -226,6 +225,12 @@ export class FormPresenter {
226
225
  : storedValue;
227
226
  const dirty = storedValue !== value;
228
227
  assertExists(revert, 'changing field directly not supported {}', valuePath);
228
+ if (ignoreDefaultValue) {
229
+ const { value: defaultDisplayValue, } = convert(create(valuePath, model.value), valuePath, model.value);
230
+ if (defaultDisplayValue === value) {
231
+ return true;
232
+ }
233
+ }
229
234
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
230
235
  const conversion = revert(value, valuePath, model.value);
231
236
  return runInAction(function () {
@@ -293,9 +298,6 @@ export class FormPresenter {
293
298
  }, true);
294
299
  });
295
300
  }
296
- createModel(value) {
297
- return new FormModel(this.type, value, this.adapters);
298
- }
299
301
  }
300
302
  export class FormModel {
301
303
  type;
@@ -1,14 +1,34 @@
1
1
  import { type ReadonlyTypeOfType, type ValueOfType } from '@strictly/define';
2
2
  import { type FieldsViewProps } from 'core/props';
3
+ import { type ComponentType } from 'react';
4
+ import { type UnsafePartialComponent } from 'util/partial';
3
5
  import { type FormPresenter } from './form_presenter';
4
- import { type ValuePathsOfPresenter } from './types';
6
+ import { type FormFieldsOfPresenter, type ValuePathsOfPresenter } from './types';
5
7
  type ValueOfPresenter<P extends FormPresenter<any, any, any, any>> = P extends FormPresenter<infer T, any, any, any> ? ValueOfType<ReadonlyTypeOfType<T>> : never;
6
8
  type ModelOfPresenter<P extends FormPresenter<any, any, any, any>> = ReturnType<P['createModel']>;
7
- export declare function useDefaultMobxFormHooks<P extends FormPresenter<any, any, any, any>>(presenter: P, value: ValueOfPresenter<P>, { onValidFieldSubmit, onValidFormSubmit, }: {
9
+ export declare function useDefaultMobxFormHooks<P extends FormPresenter<any, any, any, any>, C extends ComponentType<FieldsViewProps<F>>, F extends FormFieldsOfPresenter<P> = FormFieldsOfPresenter<P>>(presenter: P, value: ValueOfPresenter<P>, options?: {
8
10
  onValidFieldSubmit?: <Path extends ValuePathsOfPresenter<P>>(model: ModelOfPresenter<P>, valuePath: Path) => void;
9
11
  onValidFormSubmit?: (model: ModelOfPresenter<P>, value: ValueOfPresenter<P>) => void;
10
12
  }): {
11
13
  model: ModelOfPresenter<P>;
12
- onFormSubmit?: (value: ValueOfPresenter<P>) => void;
13
- } & Omit<FieldsViewProps<ModelOfPresenter<P>['fields']>, 'fields'>;
14
+ FormFields?: UnsafePartialComponent<C, FieldsViewProps<F>>;
15
+ onFormSubmit: () => void;
16
+ onFieldValueChange<K extends keyof F>(this: void, key: K, value: F[K]['value']): void;
17
+ onFieldFocus?(this: void, key: keyof F): void;
18
+ onFieldBlur?(this: void, key: keyof F): void;
19
+ onFieldSubmit?(this: void, key: keyof F): boolean | void;
20
+ };
21
+ export declare function useDefaultMobxFormHooks<P extends FormPresenter<any, any, any, any>, C extends ComponentType<FieldsViewProps<F>>, F extends FormFieldsOfPresenter<P> = FormFieldsOfPresenter<P>>(presenter: P, value: ValueOfPresenter<P>, options: {
22
+ onValidFieldSubmit?: <Path extends ValuePathsOfPresenter<P>>(model: ModelOfPresenter<P>, valuePath: Path) => void;
23
+ onValidFormSubmit?: (model: ModelOfPresenter<P>, value: ValueOfPresenter<P>) => void;
24
+ FormFieldsView: C;
25
+ }): {
26
+ model: ModelOfPresenter<P>;
27
+ FormFields: UnsafePartialComponent<C, FieldsViewProps<F>>;
28
+ onFormSubmit: () => void;
29
+ onFieldValueChange<K extends keyof F>(this: void, key: K, value: F[K]['value']): void;
30
+ onFieldFocus?(this: void, key: keyof F): void;
31
+ onFieldBlur?(this: void, key: keyof F): void;
32
+ onFieldSubmit?(this: void, key: keyof F): boolean | void;
33
+ };
14
34
  export {};
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useMemo, } from 'react';
2
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
- export function useDefaultMobxFormHooks(presenter, value, { onValidFieldSubmit, onValidFormSubmit, }) {
2
+ import { createUnsafePartialObserverComponent, } from 'util/partial';
3
+ export function useDefaultMobxFormHooks(presenter, value, { onValidFieldSubmit, onValidFormSubmit, FormFieldsView, } = {}) {
4
4
  const model = useMemo(function () {
5
5
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
6
6
  return presenter.createModel(value);
@@ -31,7 +31,7 @@ export function useDefaultMobxFormHooks(presenter, value, { onValidFieldSubmit,
31
31
  // TODO debounce?
32
32
  setTimeout(function () {
33
33
  if (presenter.isValuePathActive(model, path)) {
34
- presenter.validateField(model, path);
34
+ presenter.validateField(model, path, true);
35
35
  }
36
36
  }, 100);
37
37
  }, [
@@ -47,11 +47,32 @@ export function useDefaultMobxFormHooks(presenter, value, { onValidFieldSubmit,
47
47
  model,
48
48
  onValidFormSubmit,
49
49
  ]);
50
+ const FormFields = useMemo(() => {
51
+ if (FormFieldsView == null) {
52
+ return undefined;
53
+ }
54
+ return createUnsafePartialObserverComponent(FormFieldsView, () => {
55
+ return {
56
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
57
+ fields: model.fields,
58
+ onFieldBlur,
59
+ onFieldSubmit,
60
+ onFieldValueChange,
61
+ };
62
+ });
63
+ }, [
64
+ model,
65
+ FormFieldsView,
66
+ onFieldBlur,
67
+ onFieldSubmit,
68
+ onFieldValueChange,
69
+ ]);
50
70
  return {
51
71
  model,
52
72
  onFieldValueChange,
53
73
  onFieldSubmit,
54
74
  onFieldBlur,
55
75
  onFormSubmit,
76
+ FormFields,
56
77
  };
57
78
  }
@@ -9,6 +9,11 @@ import { prototypingFieldValueFactory } from 'field_value_factories/prototyping_
9
9
  import { UnreliableFieldConversionType, } from 'types/field_converters';
10
10
  import { createMockedAdapter, resetMockAdapter, } from './fixtures';
11
11
  const IS_NAN_ERROR = 1;
12
+ class TestFormPresenter extends FormPresenter {
13
+ createModel(value) {
14
+ return new FormModel(this.type, value, this.adapters);
15
+ }
16
+ }
12
17
  const originalIntegerToStringAdapter = adapterFromTwoWayConverter(new IntegerToStringConverter(IS_NAN_ERROR), prototypingFieldValueFactory(0));
13
18
  const originalBooleanToBooleanAdapter = identityAdapter(false);
14
19
  describe('all', function () {
@@ -286,7 +291,7 @@ describe('all', function () {
286
291
  const adapters = {
287
292
  $: integerToStringAdapter,
288
293
  };
289
- const presenter = new FormPresenter(typeDef, adapters);
294
+ const presenter = new TestFormPresenter(typeDef, adapters);
290
295
  const originalValue = 2;
291
296
  let model;
292
297
  beforeEach(function () {
@@ -383,7 +388,7 @@ describe('all', function () {
383
388
  const converters = {
384
389
  '$.*': integerToStringAdapter,
385
390
  };
386
- const presenter = new FormPresenter(typeDef, converters);
391
+ const presenter = new TestFormPresenter(typeDef, converters);
387
392
  let originalValue;
388
393
  let model;
389
394
  beforeEach(function () {
@@ -661,7 +666,7 @@ describe('all', function () {
661
666
  $: adapterFromTwoWayConverter(new NullableToBooleanConverter(type, [1], null)),
662
667
  '$.*': integerToStringAdapter,
663
668
  };
664
- const presenter = new FormPresenter(type, adapters);
669
+ const presenter = new TestFormPresenter(type, adapters);
665
670
  let originalValue;
666
671
  let model;
667
672
  beforeEach(function () {
@@ -709,7 +714,7 @@ describe('all', function () {
709
714
  '$.x:a': identityAdapter(0).narrow,
710
715
  '$.y:b': identityAdapter(false).narrow,
711
716
  };
712
- const presenter = new FormPresenter(type, adapters);
717
+ const presenter = new TestFormPresenter(type, adapters);
713
718
  describe('isValuePathActive', function () {
714
719
  describe('discriminator x', function () {
715
720
  const model = presenter.createModel({
@@ -766,7 +771,7 @@ describe('all', function () {
766
771
  $: integerToStringAdapter,
767
772
  '$.fake': booleanToBooleanAdapter,
768
773
  };
769
- const presenter = new FormPresenter(typeDef, converters);
774
+ const presenter = new TestFormPresenter(typeDef, converters);
770
775
  let originalValue;
771
776
  let model;
772
777
  beforeEach(function () {
@@ -1,9 +1,9 @@
1
1
  import { type StringConcatOf } from '@strictly/base';
2
- import { type Type, type ValueOfType } from '@strictly/define';
2
+ import { type ReadonlyTypeOfType, type Type, type ValueOfType } from '@strictly/define';
3
3
  import { type ErrorOfFieldAdapter, type FieldAdapter, type FromOfFieldAdapter, type ToOfFieldAdapter, type ValuePathOfFieldAdapter } from './field_adapter';
4
4
  type SubFormFieldAdapter<F extends FieldAdapter, ValuePath extends string, Context> = FieldAdapter<FromOfFieldAdapter<F>, ToOfFieldAdapter<F>, ErrorOfFieldAdapter<F>, ValuePathOfFieldAdapter<F> extends StringConcatOf<'$', infer ValuePathSuffix> ? `${ValuePath}${ValuePathSuffix}` : string, Context>;
5
5
  type SubFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, TypePath extends string, ValuePath extends string, Context> = {
6
6
  [K in keyof SubAdapters as K extends StringConcatOf<'$', infer TypePathSuffix> ? `${TypePath}${TypePathSuffix}` : never]: SubFormFieldAdapter<SubAdapters[K], ValuePath, Context>;
7
7
  };
8
- export declare function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, TypePath extends string, TypePathsToValuePaths extends Record<TypePath, string>, ContextType extends Type>(subAdapters: SubAdapters, parentTypePath: TypePath, contextType: ContextType): SubFormFieldAdapters<SubAdapters, TypePath, TypePathsToValuePaths[TypePath], ValueOfType<ContextType>>;
8
+ export declare function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, TypePath extends string, TypePathsToValuePaths extends Record<TypePath, string>, ContextType extends Type>(subAdapters: SubAdapters, parentTypePath: TypePath, contextType: ContextType): SubFormFieldAdapters<SubAdapters, TypePath, TypePathsToValuePaths[TypePath], ValueOfType<ReadonlyTypeOfType<ContextType>>>;
9
9
  export {};
@@ -40,12 +40,12 @@ export function chainUnreliableFieldConverter(from, to) {
40
40
  }
41
41
  export function chainAnnotatedFieldConverter(from, to) {
42
42
  return function (value, valuePath, context) {
43
- const { required: intermediateRequired, readonly: intermediateDisabled, value: intermediateValue, } = from(value, valuePath, context);
44
- const { required: finalRequired, readonly: finalDisabled, value: finalValue, } = to(intermediateValue, valuePath, context);
43
+ const { required: intermediateRequired, readonly: intermediateReadonly, value: intermediateValue, } = from(value, valuePath, context);
44
+ const { required: finalRequired, readonly: finalReadonly, value: finalValue, } = to(intermediateValue, valuePath, context);
45
45
  return {
46
46
  value: finalValue,
47
47
  required: intermediateRequired || finalRequired,
48
- readonly: intermediateDisabled || finalDisabled,
48
+ readonly: intermediateReadonly || finalReadonly,
49
49
  };
50
50
  };
51
51
  }
@@ -1,7 +1,15 @@
1
+ import { type StringConcatOf } from '@strictly/base';
1
2
  import type { FieldsViewProps } from 'core/props';
2
3
  import type { ComponentType } from 'react';
3
4
  import type { AllFieldsOfFields } from 'types/all_fields_of_fields';
4
5
  import type { Fields } from 'types/field';
5
6
  import type { SubFormFields } from 'types/sub_form_fields';
6
7
  import type { MantineFieldComponent } from './types';
7
- export declare function createFieldsView<F extends Fields, K extends keyof AllFieldsOfFields<F>, P extends FieldsViewProps<Fields> = FieldsViewProps<SubFormFields<F, K>>>(valuePath: K, FieldsView: ComponentType<P>, observableProps: FieldsViewProps<F>): MantineFieldComponent<FieldsViewProps<P['fields']>, P, never>;
8
+ export type CallbackMapper<ValuePath extends string> = {
9
+ <Cb extends (...args: any[]) => any>(cb: Cb): Parameters<Cb> extends [infer SubFormValuePath extends string, ...(infer Rest)] ? SubFormValuePath extends StringConcatOf<ValuePath, infer Postfix> ? (valuePath: `$${Postfix}`, ...rest: Rest) => ReturnType<Cb> : never : never;
10
+ };
11
+ export type FieldsView<ValuePath extends string = string, C extends ComponentType<any> = ComponentType<any>> = {
12
+ Component: C;
13
+ callbackMapper: CallbackMapper<ValuePath>;
14
+ };
15
+ export declare function createFieldsView<F extends Fields, K extends keyof AllFieldsOfFields<F>, P extends FieldsViewProps<Fields> = FieldsViewProps<SubFormFields<F, K>>>(valuePath: K, FieldsView: ComponentType<P>, observableProps: FieldsViewProps<F>): FieldsView<K, MantineFieldComponent<FieldsViewProps<P['fields']>, P, never>>;
@@ -23,7 +23,7 @@ export function createFieldsView(valuePath, FieldsView, observableProps) {
23
23
  observableProps.onFieldSubmit?.(toKey(subKey));
24
24
  }
25
25
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
26
- return observer(function (props) {
26
+ const Component = observer(function (props) {
27
27
  // convert fields to sub-fields
28
28
  const subFields = Object.entries(observableProps.fields).reduce((acc, [fieldKey, fieldValue,]) => {
29
29
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -36,4 +36,16 @@ export function createFieldsView(valuePath, FieldsView, observableProps) {
36
36
  {});
37
37
  return (_jsx(FieldsView, { ...props, fields: subFields, onFieldBlur: onFieldBlur, onFieldFocus: onFieldFocus, onFieldSubmit: onFieldSubmit, onFieldValueChange: onFieldValueChange }));
38
38
  });
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions
40
+ const callbackMapper = ((callback) => {
41
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+ return (subFormValuePath, ...args) => {
43
+ const valuePath = toKey(subFormValuePath);
44
+ return callback(valuePath, ...args);
45
+ };
46
+ });
47
+ return {
48
+ Component,
49
+ callbackMapper,
50
+ };
39
51
  }
@@ -1,6 +1,10 @@
1
1
  import { type ComponentType } from 'react';
2
- export type ErrorRendererProps<E> = {
2
+ import { type ErrorOfField } from 'types/error_of_field';
3
+ import { type Fields } from 'types/field';
4
+ type InternalErrorRendererProps<E> = {
3
5
  error: E;
4
6
  };
5
- export type ErrorRenderer<E = any> = ComponentType<ErrorRendererProps<E>>;
6
- export declare function DefaultErrorRenderer({ error, }: ErrorRendererProps<any>): string;
7
+ export type ErrorRendererProps<F extends Fields, K extends keyof Fields> = InternalErrorRendererProps<ErrorOfField<F[K]>>;
8
+ export type ErrorRenderer<E = any> = ComponentType<InternalErrorRendererProps<E>>;
9
+ export declare function DefaultErrorRenderer({ error, }: InternalErrorRendererProps<any>): string;
10
+ export {};
@@ -11,6 +11,7 @@ import { type StringFieldsOfFields } from 'types/string_fields_of_fields';
11
11
  import { type SubFormFields } from 'types/sub_form_fields';
12
12
  import { type ValueTypeOfField } from 'types/value_type_of_field';
13
13
  import { type SuppliedCheckboxProps } from './create_checkbox';
14
+ import { type FieldsView } from './create_fields_view';
14
15
  import { DefaultList, type SuppliedListProps } from './create_list';
15
16
  import { type SuppliedPillProps } from './create_pill';
16
17
  import { type SuppliedRadioProps } from './create_radio';
@@ -51,7 +52,7 @@ declare class MantineFormImpl<F extends Fields> implements MantineForm<F> {
51
52
  pill<K extends keyof AllFieldsOfFields<F>>(valuePath: K): MantineFieldComponent<SuppliedPillProps, PillProps, ErrorOfField<F[K]>>;
52
53
  pill<K extends keyof AllFieldsOfFields<F>, P extends SuppliedPillProps>(valuePath: K, Pill: ComponentType<P>): MantineFieldComponent<SuppliedPillProps, P, ErrorOfField<F[K]>>;
53
54
  list<K extends keyof ListFieldsOfFields<F>>(valuePath: K): MantineFieldComponent<SuppliedListProps<`${K}.${number}`>, ComponentProps<typeof DefaultList<ElementOfArray<F[K]['value']>, K>>, never>;
54
- fieldsView<K extends keyof AllFieldsOfFields<F>, P extends FieldsViewProps<Fields> = FieldsViewProps<SubFormFields<F, K>>>(valuePath: K, FieldsView: ComponentType<P>): MantineFieldComponent<FieldsViewProps<P['fields']>, P, never>;
55
+ fieldsView<K extends keyof AllFieldsOfFields<F>, P extends FieldsViewProps<Fields> = FieldsViewProps<SubFormFields<F, K>>>(valuePath: K, FieldsView: ComponentType<P>): FieldsView<K, MantineFieldComponent<FieldsViewProps<P['fields']>, P, never>>;
55
56
  form<K extends keyof AllFieldsOfFields<F>, P extends FormProps<ValueTypeOfField<F[K]>> = FormProps<ValueTypeOfField<F[K]>>>(valuePath: K, Form: ComponentType<P>): MantineFieldComponent<FormProps<ValueTypeOfField<F[K]>>, P, never>;
56
57
  }
57
58
  export {};
@@ -4,7 +4,7 @@ import { Cache, } from '@strictly/base';
4
4
  import { observable, runInAction, } from 'mobx';
5
5
  import { useEffect, useMemo, } from 'react';
6
6
  import { createCheckbox, } from './create_checkbox';
7
- import { createFieldsView } from './create_fields_view';
7
+ import { createFieldsView, } from './create_fields_view';
8
8
  import { createForm } from './create_form';
9
9
  import { createList, DefaultList, } from './create_list';
10
10
  import { createPill, } from './create_pill';
@@ -0,0 +1,17 @@
1
+ describe('createFieldsView', () => {
2
+ describe('CallbackMapper', () => {
3
+ it('maps a root paths', () => {
4
+ const callbackMapper = null;
5
+ expectTypeOf().toEqualTypeOf();
6
+ });
7
+ it('maps a simple paths', () => {
8
+ const callbackMapper = null;
9
+ expectTypeOf().toEqualTypeOf();
10
+ });
11
+ it('maps a indexed paths', () => {
12
+ const callbackMapper = null;
13
+ expectTypeOf().toEqualTypeOf();
14
+ });
15
+ });
16
+ });
17
+ export {};
@@ -1,10 +1,14 @@
1
1
  import { type Meta, type StoryObj } from '@storybook/react';
2
2
  import { type FieldsViewProps } from 'core/props';
3
3
  import { type Field } from 'types/field';
4
- declare function Component(props: FieldsViewProps<{
4
+ export declare function ParentFieldLabel(): string;
5
+ export declare function SubFieldLabel(): string;
6
+ declare function Component({ onClickField: onClickFieldImpl, ...props }: FieldsViewProps<{
5
7
  $: Field<string, string>;
6
8
  '$.a': Field<string, string>;
7
- }>): import("react/jsx-runtime").JSX.Element;
9
+ }> & {
10
+ onClickField: (valuePath: '$' | '$.a') => void;
11
+ }): import("react/jsx-runtime").JSX.Element;
8
12
  declare const meta: Meta<typeof Component>;
9
13
  export default meta;
10
14
  type Story = StoryObj<typeof Component>;
@@ -1,21 +1,39 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Button, Stack, } from '@mantine/core';
2
+ import { Paper, Stack, Text, } from '@mantine/core';
3
3
  import { action } from '@storybook/addon-actions';
4
4
  import { useMantineFormFields } from 'mantine/hooks';
5
- const onClick = action('some button clicked');
5
+ import { useCallback, useMemo, } from 'react';
6
+ export function ParentFieldLabel() {
7
+ return '$';
8
+ }
9
+ export function SubFieldLabel() {
10
+ return '$ (child)';
11
+ }
6
12
  function ErrorRenderer({ error }) {
7
13
  return `error ${error}`;
8
14
  }
9
- function SubFieldsView(props) {
15
+ function SubFieldsView({ onClickField: onClickFieldImpl, ...props }) {
10
16
  const form = useMantineFormFields(props);
11
17
  const TextInput = form.textInput('$');
12
- return (_jsxs(Stack, { children: [_jsx(TextInput, { ErrorRenderer: ErrorRenderer, label: 'sub fields view' }), _jsx(Button, { onClick: props.onClick, children: "Bonus Button" })] }));
18
+ const onClick$ = useCallback(() => {
19
+ onClickFieldImpl('$');
20
+ }, [onClickFieldImpl]);
21
+ return (_jsx(Stack, { children: _jsx(TextInput, { ErrorRenderer: ErrorRenderer, label: SubFieldLabel(), onClick: onClick$ }) }));
13
22
  }
14
- function Component(props) {
23
+ function Component({ onClickField: onClickFieldImpl, ...props }) {
15
24
  const form = useMantineFormFields(props);
16
- const FieldsView = form.fieldsView('$.a', SubFieldsView);
25
+ const { Component, callbackMapper, } = form.fieldsView('$.a', SubFieldsView);
17
26
  const TextInput = form.textInput('$');
18
- return (_jsxs(Stack, { children: [_jsx(TextInput, { ErrorRenderer: ErrorRenderer, label: 'fields view' }), _jsx(FieldsView, { onClick: onClick })] }));
27
+ const onClick$ = useCallback(() => {
28
+ onClickFieldImpl('$');
29
+ }, [onClickFieldImpl]);
30
+ const onClickChildField = useMemo(() => {
31
+ return callbackMapper(onClickFieldImpl);
32
+ }, [
33
+ onClickFieldImpl,
34
+ callbackMapper,
35
+ ]);
36
+ return (_jsxs(Stack, { children: [_jsx(TextInput, { ErrorRenderer: ErrorRenderer, label: ParentFieldLabel(), onClick: onClick$ }), _jsxs(Paper, { p: 'sm', withBorder: true, children: [_jsx(Text, { children: "$.a" }), _jsx(Component, { onClickField: onClickChildField })] })] }));
19
37
  }
20
38
  const meta = {
21
39
  component: Component,
@@ -24,6 +42,7 @@ const meta = {
24
42
  onFieldFocus: action('onFieldFocus'),
25
43
  onFieldSubmit: action('onFieldSubmit'),
26
44
  onFieldValueChange: action('onFieldValueChange'),
45
+ onClickField: action('onClickField'),
27
46
  },
28
47
  };
29
48
  export default meta;
@@ -1,12 +1,32 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { composeStories } from '@storybook/react';
3
3
  import { toArray } from '@strictly/base';
4
- import { render, } from '@testing-library/react';
4
+ import { fireEvent, render, } from '@testing-library/react';
5
5
  import * as stories from './fields_view_hooks.stories';
6
6
  const composedStories = composeStories(stories);
7
+ const { Empty, } = composedStories;
7
8
  describe('field view hooks', function () {
8
9
  it.each(toArray(composedStories))('renders %s', function (_name, Story) {
9
10
  const wrapper = render(_jsx(Story, {}));
10
11
  expect(wrapper.container).toMatchSnapshot();
11
12
  });
13
+ describe('callbackMapper', () => {
14
+ it.each([
15
+ [
16
+ '$',
17
+ stories.ParentFieldLabel(),
18
+ ],
19
+ [
20
+ '$.a',
21
+ stories.SubFieldLabel(),
22
+ ],
23
+ ])('calls back with the correct paths for field at %s', async (valuePath, labelText) => {
24
+ const onClickField = vi.fn();
25
+ const wrapper = render(_jsx(Empty, { onClickField: onClickField }));
26
+ const element = await wrapper.findByLabelText(labelText);
27
+ fireEvent.click(element);
28
+ expect(onClickField).toHaveBeenCalledOnce();
29
+ expect(onClickField).toHaveBeenCalledWith(valuePath);
30
+ });
31
+ });
12
32
  });