@strictly/react-form 0.0.5 → 0.0.7

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 (68) hide show
  1. package/.out/core/mobx/hooks.d.ts +10 -0
  2. package/.out/core/mobx/hooks.js +47 -0
  3. package/.out/core/mobx/specs/form_presenter.tests.js +3 -6
  4. package/.out/core/mobx/specs/{merge_field_adapters_with_two_way_converter.js → merge_field_adapters_with_two_way_converter.tests.js} +15 -16
  5. package/.out/core/mobx/specs/sub_form_field_adapters.tests.js +78 -10
  6. package/.out/core/mobx/sub_form_field_adapters.d.ts +6 -4
  7. package/.out/core/mobx/sub_form_field_adapters.js +23 -2
  8. package/.out/core/props.d.ts +2 -2
  9. package/.out/index.d.ts +1 -0
  10. package/.out/index.js +1 -0
  11. package/.out/mantine/create_fields_view.d.ts +7 -0
  12. package/.out/mantine/{create_sub_form.js → create_fields_view.js} +4 -5
  13. package/.out/mantine/create_form.d.ts +7 -0
  14. package/.out/mantine/create_form.js +13 -0
  15. package/.out/mantine/hooks.d.ts +6 -4
  16. package/.out/mantine/hooks.js +17 -7
  17. package/.out/mantine/specs/checkbox_hooks.stories.d.ts +2 -2
  18. package/.out/mantine/specs/checkbox_hooks.stories.js +2 -2
  19. package/.out/mantine/specs/{sub_form_hooks.stories.d.ts → fields_view_hooks.stories.d.ts} +2 -2
  20. package/.out/mantine/specs/{sub_form_hooks.stories.js → fields_view_hooks.stories.js} +9 -8
  21. package/.out/mantine/specs/fields_view_hooks.tests.d.ts +1 -0
  22. package/.out/mantine/specs/fields_view_hooks.tests.js +12 -0
  23. package/.out/mantine/specs/form_hooks.stories.d.ts +12 -0
  24. package/.out/mantine/specs/form_hooks.stories.js +60 -0
  25. package/.out/mantine/specs/form_hooks.tests.d.ts +1 -0
  26. package/.out/mantine/specs/form_hooks.tests.js +12 -0
  27. package/.out/mantine/specs/list_hooks.stories.d.ts +2 -2
  28. package/.out/mantine/specs/list_hooks.stories.js +2 -2
  29. package/.out/mantine/specs/radio_group_hooks.stories.d.ts +2 -2
  30. package/.out/mantine/specs/radio_group_hooks.stories.js +2 -2
  31. package/.out/mantine/specs/select_hooks.stories.d.ts +2 -2
  32. package/.out/mantine/specs/select_hooks.stories.js +2 -2
  33. package/.out/mantine/specs/text_input_hooks.stories.d.ts +2 -2
  34. package/.out/mantine/specs/text_input_hooks.stories.js +2 -2
  35. package/.out/mantine/specs/value_input_hooks.stories.d.ts +2 -2
  36. package/.out/mantine/specs/value_input_hooks.stories.js +2 -2
  37. package/.out/tsconfig.tsbuildinfo +1 -1
  38. package/.turbo/turbo-build.log +8 -8
  39. package/.turbo/turbo-check-types.log +1 -1
  40. package/core/mobx/hooks.ts +94 -0
  41. package/core/mobx/specs/form_presenter.tests.ts +6 -6
  42. package/core/mobx/specs/{merge_field_adapters_with_two_way_converter.ts → merge_field_adapters_with_two_way_converter.tests.ts} +16 -16
  43. package/core/mobx/specs/sub_form_field_adapters.tests.ts +93 -10
  44. package/core/mobx/sub_form_field_adapters.ts +54 -7
  45. package/core/props.ts +2 -2
  46. package/dist/index.cjs +200 -90
  47. package/dist/index.d.cts +43 -34
  48. package/dist/index.d.ts +43 -34
  49. package/dist/index.js +186 -70
  50. package/index.ts +1 -0
  51. package/mantine/{create_sub_form.tsx → create_fields_view.tsx} +27 -16
  52. package/mantine/create_form.tsx +43 -0
  53. package/mantine/hooks.tsx +48 -14
  54. package/mantine/specs/__snapshots__/fields_view_hooks.tests.tsx.snap +460 -0
  55. package/mantine/specs/__snapshots__/form_hooks.tests.tsx.snap +273 -0
  56. package/mantine/specs/checkbox_hooks.stories.tsx +4 -4
  57. package/mantine/specs/{sub_form_hooks.stories.tsx → fields_view_hooks.stories.tsx} +23 -11
  58. package/mantine/specs/fields_view_hooks.tests.tsx +15 -0
  59. package/mantine/specs/form_hooks.stories.tsx +107 -0
  60. package/mantine/specs/form_hooks.tests.tsx +15 -0
  61. package/mantine/specs/list_hooks.stories.tsx +4 -4
  62. package/mantine/specs/radio_group_hooks.stories.tsx +4 -4
  63. package/mantine/specs/select_hooks.stories.tsx +4 -4
  64. package/mantine/specs/text_input_hooks.stories.tsx +4 -4
  65. package/mantine/specs/value_input_hooks.stories.tsx +4 -4
  66. package/package.json +1 -1
  67. package/.out/mantine/create_sub_form.d.ts +0 -6
  68. /package/.out/core/mobx/specs/{merge_field_adapters_with_two_way_converter.d.ts → merge_field_adapters_with_two_way_converter.tests.d.ts} +0 -0
@@ -0,0 +1,10 @@
1
+ import { type ReadonlyTypeOfType, type ValueOfType } from '@strictly/define';
2
+ import { type FieldsViewProps } from 'core/props';
3
+ import { type FormPresenter } from './form_presenter';
4
+ import { type ValuePathsOfPresenter } from './types';
5
+ type ValueOfPresenter<P extends FormPresenter<any, any, any, any>> = P extends FormPresenter<infer T, any, any, any> ? ValueOfType<ReadonlyTypeOfType<T>> : never;
6
+ 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>, onValidSubmit?: <Path extends ValuePathsOfPresenter<P>>(model: ModelOfPresenter<P>, valuePath: Path) => void): {
8
+ model: ModelOfPresenter<P>;
9
+ } & Omit<FieldsViewProps<ModelOfPresenter<P>['fields']>, 'fields'>;
10
+ export {};
@@ -0,0 +1,47 @@
1
+ import { useCallback, useMemo, } from 'react';
2
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
+ export function useDefaultMobxFormHooks(presenter, value, onValidSubmit) {
4
+ const model = useMemo(function () {
5
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
6
+ return presenter.createModel(value);
7
+ }, [
8
+ presenter,
9
+ value,
10
+ ]);
11
+ const onFieldValueChange = useCallback(function (path, value) {
12
+ presenter.clearFieldError(model, path);
13
+ presenter.setFieldValue(model, path, value);
14
+ }, [
15
+ presenter,
16
+ model,
17
+ ]);
18
+ const onFieldSubmit = useCallback(function (valuePath) {
19
+ if (presenter.validateField(model, valuePath)) {
20
+ onValidSubmit?.(model, valuePath);
21
+ }
22
+ return false;
23
+ }, [
24
+ presenter,
25
+ model,
26
+ onValidSubmit,
27
+ ]);
28
+ const onFieldBlur = useCallback(function (path) {
29
+ // work around potential loss of focus prior to state potentially invalidating change triggering
30
+ // (e.g. changing a discriminator)
31
+ // TODO debounce?
32
+ setTimeout(function () {
33
+ if (presenter.isValuePathActive(model, path)) {
34
+ presenter.validateField(model, path);
35
+ }
36
+ }, 100);
37
+ }, [
38
+ presenter,
39
+ model,
40
+ ]);
41
+ return {
42
+ model,
43
+ onFieldValueChange,
44
+ onFieldSubmit,
45
+ onFieldBlur,
46
+ };
47
+ }
@@ -21,18 +21,16 @@ describe('all', function () {
21
21
  describe('FlattenedTypePathsToConvertersOf', function () {
22
22
  describe('record', function () {
23
23
  const typeDef = record(numberType);
24
- let t;
25
24
  it('equals expected type', function () {
26
- expectTypeOf(t).toEqualTypeOf();
25
+ expectTypeOf().toEqualTypeOf();
27
26
  });
28
27
  });
29
28
  describe('object', function () {
30
29
  const typeDef = object()
31
30
  .field('x', stringType)
32
31
  .field('y', booleanType);
33
- let t;
34
32
  it('equals expected type', function () {
35
- expectTypeOf(t).toEqualTypeOf();
33
+ expectTypeOf().toEqualTypeOf();
36
34
  });
37
35
  it('matches representative adapters', function () {
38
36
  expectTypeOf().toMatchTypeOf();
@@ -50,9 +48,8 @@ describe('all', function () {
50
48
  '$.b': '$.y',
51
49
  '$.c': '$.z',
52
50
  };
53
- let t;
54
51
  it('equals expected type', function () {
55
- expectTypeOf(t).toEqualTypeOf();
52
+ expectTypeOf().toEqualTypeOf();
56
53
  });
57
54
  });
58
55
  });
@@ -9,9 +9,8 @@ const error3 = Symbol();
9
9
  const error4 = Symbol();
10
10
  const context = Symbol();
11
11
  describe('MergedOfFieldAdapterWithTwoWayConverter', function () {
12
- let m;
13
12
  it('merges the errors', function () {
14
- expectTypeOf().toEqualTypeOf(m);
13
+ expectTypeOf().toEqualTypeOf();
15
14
  });
16
15
  });
17
16
  const originalIntegerAdapter = identityAdapter(0);
@@ -69,20 +68,20 @@ describe('mergeFieldAdaptersWithTwoWayConverter', function () {
69
68
  beforeEach(function () {
70
69
  result = merged.booleanAdapter.revert(true, 'booleanAdapter', context);
71
70
  });
72
- });
73
- it('returns the same value on revert', function () {
74
- expect(result).toEqual(expect.objectContaining({
75
- value: true,
76
- type: UnreliableFieldConversionType.Success,
77
- }));
78
- });
79
- it('calls the mocked converter', function () {
80
- expect(converter.revert).toHaveBeenCalledOnce();
81
- expect(converter.revert).toHaveBeenCalledWith(true, 'booleanAdapter', context);
82
- });
83
- it('calls the mocked adapter', function () {
84
- expect(booleanAdapter.revert).toHaveBeenCalledOnce();
85
- expect(booleanAdapter.revert).toHaveBeenCalledWith(true, 'booleanAdapter', context);
71
+ it('returns the same value on revert', function () {
72
+ expect(result).toEqual(expect.objectContaining({
73
+ value: true,
74
+ type: UnreliableFieldConversionType.Success,
75
+ }));
76
+ });
77
+ it('calls the mocked converter', function () {
78
+ expect(converter.revert).toHaveBeenCalledOnce();
79
+ expect(converter.revert).toHaveBeenCalledWith(true, 'booleanAdapter', context);
80
+ });
81
+ it('calls the mocked adapter', function () {
82
+ expect(booleanAdapter.revert).toHaveBeenCalledOnce();
83
+ expect(booleanAdapter.revert).toHaveBeenCalledWith(true, 'booleanAdapter', context);
84
+ });
86
85
  });
87
86
  });
88
87
  });
@@ -1,10 +1,14 @@
1
+ import { numberType, object, stringType, } from '@strictly/define';
1
2
  import { subFormFieldAdapters, } from 'core/mobx/sub_form_field_adapters';
3
+ import { UnreliableFieldConversionType } from 'types/field_converters';
2
4
  import { mockDeep } from 'vitest-mock-extended';
3
5
  describe('subFormFieldAdapters', () => {
4
- const fieldAdapter1 = mockDeep();
5
- const fieldAdapter2 = mockDeep();
6
+ const mockedFieldAdapter1 = mockDeep();
7
+ const fieldAdapter1 = mockedFieldAdapter1;
8
+ const mockedFieldAdapter2 = mockDeep();
9
+ const fieldAdapter2 = mockedFieldAdapter2;
6
10
  describe('empty value', () => {
7
- const adapters = subFormFieldAdapters({}, '$.a');
11
+ const adapters = subFormFieldAdapters({}, '$.a', stringType);
8
12
  it('equals expected type', () => {
9
13
  expectTypeOf(adapters).toEqualTypeOf();
10
14
  });
@@ -13,28 +17,92 @@ describe('subFormFieldAdapters', () => {
13
17
  });
14
18
  });
15
19
  describe('single adapter', () => {
20
+ const type = object().field('a', stringType);
16
21
  const adapters = subFormFieldAdapters({
17
22
  $: fieldAdapter1,
18
- }, '$.a');
23
+ }, '$.a', type);
19
24
  it('equals expected type', () => {
20
- expectTypeOf(adapters).toEqualTypeOf();
25
+ // TODO toEqualTypeOf (seems to be a TS error)
26
+ expectTypeOf(adapters).toMatchTypeOf();
21
27
  });
22
28
  it('equals expected value', () => {
23
- expect(adapters).toEqual({ '$.a': fieldAdapter1 });
29
+ expect(adapters).toEqual({ '$.a': expect.anything() });
30
+ });
31
+ it('calls convert with the correct paths and values', () => {
32
+ const mockedReturnedValue = {
33
+ value: false,
34
+ required: false,
35
+ readonly: false,
36
+ };
37
+ mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue);
38
+ const returnedValue = adapters['$.a'].convert('x', '$.a', { a: 'y' });
39
+ expect(fieldAdapter1.convert).toHaveBeenCalledWith('x', '$', 'y');
40
+ expect(returnedValue).toEqual(mockedReturnedValue);
41
+ });
42
+ it('calls revert with the correct paths and values', () => {
43
+ const mockedReturnedValue = {
44
+ type: UnreliableFieldConversionType.Success,
45
+ value: 'ok',
46
+ };
47
+ mockedFieldAdapter1.revert.mockReturnValue(mockedReturnedValue);
48
+ const returnedValue = adapters['$.a'].revert?.(true, '$.a', { a: 'y' });
49
+ expect(fieldAdapter1.revert).toHaveBeenCalledWith(true, '$', 'y');
50
+ expect(returnedValue).toEqual(mockedReturnedValue);
51
+ });
52
+ it('calls create with the correct paths and values', () => {
53
+ const mockedReturnedValue = 'x';
54
+ mockedFieldAdapter1.create.mockReturnValue(mockedReturnedValue);
55
+ const returnedValue = adapters['$.a'].create('$.a', { a: 'y' });
56
+ expect(fieldAdapter1.create).toHaveBeenCalledWith('$', 'y');
57
+ expect(returnedValue).toEqual(mockedReturnedValue);
24
58
  });
25
59
  });
26
60
  describe('multiple adapters', () => {
61
+ const type = object()
62
+ .field('a', object().field('x', stringType).field('y', numberType));
27
63
  const adapters = subFormFieldAdapters({
28
64
  '$.x': fieldAdapter1,
29
65
  '$.y': fieldAdapter2,
30
- }, '$.a');
66
+ }, '$.a', type);
31
67
  it('equals expected type', () => {
32
- expectTypeOf(adapters).toEqualTypeOf();
68
+ // TODO toEqualTypeOf (seems to be a TS error)
69
+ expectTypeOf(adapters).toMatchTypeOf();
33
70
  });
34
71
  it('equals expected value', () => {
35
72
  expect(adapters).toEqual({
36
- '$.a.x': fieldAdapter1,
37
- '$.a.y': fieldAdapter2,
73
+ '$.a.x': expect.anything(),
74
+ '$.a.y': expect.anything(),
75
+ });
76
+ });
77
+ describe('calls convert with correct paths and values', () => {
78
+ const subContext = {
79
+ x: 'a',
80
+ y: 1,
81
+ };
82
+ const context = {
83
+ a: subContext,
84
+ };
85
+ it('calls $.a.x', () => {
86
+ const mockedReturnedValue = {
87
+ value: true,
88
+ readonly: true,
89
+ required: false,
90
+ };
91
+ mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue);
92
+ const returnedValue = adapters['$.a.x'].convert('b', '$.a.x', context);
93
+ expect(fieldAdapter1.convert).toHaveBeenCalledWith('b', '$.x', subContext);
94
+ expect(returnedValue).toEqual(mockedReturnedValue);
95
+ });
96
+ it('calls $.a.y', () => {
97
+ const mockedReturnedValue = {
98
+ value: false,
99
+ readonly: false,
100
+ required: false,
101
+ };
102
+ mockedFieldAdapter2.convert.mockReturnValue(mockedReturnedValue);
103
+ const returnedValue = adapters['$.a.y'].convert(2, '$.a.y', context);
104
+ expect(fieldAdapter2.convert).toHaveBeenCalledWith(2, '$.y', subContext);
105
+ expect(returnedValue).toEqual(mockedReturnedValue);
38
106
  });
39
107
  });
40
108
  });
@@ -1,7 +1,9 @@
1
1
  import { type StringConcatOf } from '@strictly/base';
2
- import { type FieldAdapter } from './field_adapter';
3
- type SubFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string> = {
4
- [K in keyof SubAdapters as K extends StringConcatOf<'$', infer S> ? `${P}${S}` : never]: SubAdapters[K];
2
+ import { type Type, type ValueOfType } from '@strictly/define';
3
+ import { type ErrorOfFieldAdapter, type FieldAdapter, type FromOfFieldAdapter, type ToOfFieldAdapter } from './field_adapter';
4
+ type SubFormFieldAdapter<F extends FieldAdapter, P extends string, Context> = FieldAdapter<FromOfFieldAdapter<F>, ToOfFieldAdapter<F>, ErrorOfFieldAdapter<F>, P, Context>;
5
+ type SubFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string, Context> = {
6
+ [K in keyof SubAdapters as K extends StringConcatOf<'$', infer S> ? `${P}${S}` : never]: K extends StringConcatOf<'$', infer S> ? SubFormFieldAdapter<SubAdapters[K], `${P}${S}`, Context> : never;
5
7
  };
6
- export declare function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string>(subAdapters: SubAdapters, prefix: P): SubFormFieldAdapters<SubAdapters, P>;
8
+ export declare function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string, ContextType extends Type>(subAdapters: SubAdapters, prefix: P, contextType: ContextType): SubFormFieldAdapters<SubAdapters, P, ValueOfType<ContextType>>;
7
9
  export {};
@@ -1,8 +1,29 @@
1
- export function subFormFieldAdapters(subAdapters, prefix) {
1
+ import { flattenValuesOfType, } from '@strictly/define';
2
+ export function subFormFieldAdapters(subAdapters, prefix, contextType) {
3
+ function getSubValuePathAndContext(valuePath, context) {
4
+ const subValuePath = valuePath.replace(prefix, '$');
5
+ const subContext = flattenValuesOfType(contextType, context)[prefix];
6
+ return [
7
+ subValuePath,
8
+ subContext,
9
+ ];
10
+ }
2
11
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
3
12
  return Object.entries(subAdapters).reduce((acc, [subKey, subValue,]) => {
4
13
  const key = subKey.replace('$', prefix);
5
- acc[key] = subValue;
14
+ // adapt field adapter with new path and context
15
+ const adaptedAdapter = {
16
+ convert: (from, valuePath, context) => {
17
+ return subValue.convert(from, ...getSubValuePathAndContext(valuePath, context));
18
+ },
19
+ create: (valuePath, context) => {
20
+ return subValue.create(...getSubValuePathAndContext(valuePath, context));
21
+ },
22
+ revert: subValue.revert && ((from, valuePath, context) => {
23
+ return subValue.revert(from, ...getSubValuePathAndContext(valuePath, context));
24
+ }),
25
+ };
26
+ acc[key] = adaptedAdapter;
6
27
  return acc;
7
28
  }, {});
8
29
  }
@@ -1,12 +1,12 @@
1
1
  import { type Fields } from 'types/field';
2
- export type FormProps<F extends Fields> = {
2
+ export type FieldsViewProps<F extends Fields> = {
3
3
  fields: F;
4
4
  onFieldValueChange<K extends keyof F>(this: void, key: K, value: F[K]['value']): void;
5
5
  onFieldFocus?(this: void, key: keyof F): void;
6
6
  onFieldBlur?(this: void, key: keyof F): void;
7
7
  onFieldSubmit?(this: void, key: keyof F): boolean | void;
8
8
  };
9
- export type EditorProps<O> = {
9
+ export type FormProps<O> = {
10
10
  value: O;
11
11
  onValueChange: (value: O) => void;
12
12
  };
package/.out/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export * from './core/mobx/field_adapters_of_values';
4
4
  export * from './core/mobx/flattened_adapters_of_fields';
5
5
  export * from './core/mobx/form_fields_of_field_adapters';
6
6
  export * from './core/mobx/form_presenter';
7
+ export * from './core/mobx/hooks';
7
8
  export * from './core/mobx/merge_field_adapters_with_two_way_converter';
8
9
  export * from './core/mobx/merge_field_adapters_with_validators';
9
10
  export * from './core/mobx/sub_form_field_adapters';
package/.out/index.js CHANGED
@@ -4,6 +4,7 @@ export * from './core/mobx/field_adapters_of_values';
4
4
  export * from './core/mobx/flattened_adapters_of_fields';
5
5
  export * from './core/mobx/form_fields_of_field_adapters';
6
6
  export * from './core/mobx/form_presenter';
7
+ export * from './core/mobx/hooks';
7
8
  export * from './core/mobx/merge_field_adapters_with_two_way_converter';
8
9
  export * from './core/mobx/merge_field_adapters_with_validators';
9
10
  export * from './core/mobx/sub_form_field_adapters';
@@ -0,0 +1,7 @@
1
+ import type { FieldsViewProps } from 'core/props';
2
+ import type { ComponentType } from 'react';
3
+ import type { AllFieldsOfFields } from 'types/all_fields_of_fields';
4
+ import type { Fields } from 'types/field';
5
+ import type { SubFormFields } from 'types/sub_form_fields';
6
+ 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>;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { observer } from 'mobx-react';
3
- export function createSubForm(valuePath, SubForm, observableProps) {
3
+ export function createFieldsView(valuePath, FieldsView, observableProps) {
4
4
  function toKey(subKey) {
5
5
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
6
6
  return subKey.replace('$', valuePath);
@@ -22,7 +22,8 @@ export function createSubForm(valuePath, SubForm, observableProps) {
22
22
  function onFieldSubmit(subKey) {
23
23
  observableProps.onFieldSubmit?.(toKey(subKey));
24
24
  }
25
- return observer(function () {
25
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
26
+ return observer(function (props) {
26
27
  // convert fields to sub-fields
27
28
  const subFields = Object.entries(observableProps.fields).reduce((acc, [fieldKey, fieldValue,]) => {
28
29
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -31,9 +32,7 @@ export function createSubForm(valuePath, SubForm, observableProps) {
31
32
  }
32
33
  return acc;
33
34
  }, {});
34
- return (_jsx(SubForm
35
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
36
- , {
35
+ return (_jsx(FieldsView, { ...props,
37
36
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
38
37
  fields: subFields, onFieldBlur: onFieldBlur, onFieldFocus: onFieldFocus, onFieldSubmit: onFieldSubmit, onFieldValueChange: onFieldValueChange }));
39
38
  });
@@ -0,0 +1,7 @@
1
+ import { type FieldsViewProps, type FormProps } from 'core/props';
2
+ import { type ComponentType } from 'react';
3
+ import { type AllFieldsOfFields } from 'types/all_fields_of_fields';
4
+ import { type Fields } from 'types/field';
5
+ import { type ValueTypeOfField } from 'types/value_type_of_field';
6
+ import { type MantineFieldComponent } from './types';
7
+ export declare function createForm<F extends Fields, K extends keyof AllFieldsOfFields<F>, P extends FormProps<ValueTypeOfField<F[K]>> = FormProps<ValueTypeOfField<F[K]>>>(valuePath: K, Form: ComponentType<P>, observableProps: FieldsViewProps<F>): MantineFieldComponent<FormProps<ValueTypeOfField<F[K]>>, P>;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { observer } from 'mobx-react';
3
+ import { useCallback, } from 'react';
4
+ export function createForm(valuePath, Form, observableProps) {
5
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
6
+ return observer((props) => {
7
+ const { value } = observableProps.fields[valuePath];
8
+ const onValueChange = useCallback((value) => {
9
+ observableProps.onFieldValueChange(valuePath, value);
10
+ }, []);
11
+ return (_jsx(Form, { ...props, onValueChange: onValueChange, value: value }));
12
+ });
13
+ }
@@ -1,6 +1,6 @@
1
1
  import { type CheckboxProps, type PillProps, type RadioGroupProps, type RadioProps, type SelectProps, type TextInputProps } from '@mantine/core';
2
2
  import { type ElementOfArray } from '@strictly/base';
3
- import { type FormProps } from 'core/props';
3
+ import { type FieldsViewProps, type FormProps } from 'core/props';
4
4
  import { type ComponentProps, type ComponentType } from 'react';
5
5
  import { type AllFieldsOfFields } from 'types/all_fields_of_fields';
6
6
  import { type BooleanFieldsOfFields } from 'types/boolean_fields_of_fields';
@@ -21,7 +21,7 @@ import { type MantineFieldComponent, type MantineForm } from './types';
21
21
  declare function SimpleSelect(props: SelectProps & {
22
22
  onChange?: (value: string | null) => void;
23
23
  }): import("react/jsx-runtime").JSX.Element;
24
- export declare function useMantineForm<F extends Fields>({ onFieldValueChange, onFieldBlur, onFieldFocus, onFieldSubmit, fields, }: FormProps<F>): MantineFormImpl<F>;
24
+ export declare function useMantineFormFields<F extends Fields>({ onFieldValueChange, onFieldBlur, onFieldFocus, onFieldSubmit, fields, }: FieldsViewProps<F>): MantineFormImpl<F>;
25
25
  declare class MantineFormImpl<F extends Fields> implements MantineForm<F> {
26
26
  private readonly textInputCache;
27
27
  private readonly valueInputCache;
@@ -30,7 +30,8 @@ declare class MantineFormImpl<F extends Fields> implements MantineForm<F> {
30
30
  private readonly radioCache;
31
31
  private readonly pillCache;
32
32
  private readonly listCache;
33
- private readonly subFormCache;
33
+ private readonly fieldsViewCache;
34
+ private readonly formCache;
34
35
  accessor fields: F;
35
36
  onFieldValueChange: <K extends keyof F>(this: void, key: K, value: F[K]['value']) => void;
36
37
  onFieldFocus: ((this: void, key: keyof F) => void) | undefined;
@@ -50,6 +51,7 @@ declare class MantineFormImpl<F extends Fields> implements MantineForm<F> {
50
51
  pill<K extends keyof AllFieldsOfFields<F>>(valuePath: K): MantineFieldComponent<SuppliedPillProps, PillProps, ErrorOfField<F[K]>>;
51
52
  pill<K extends keyof AllFieldsOfFields<F>, P extends SuppliedPillProps>(valuePath: K, Pill: ComponentType<P>): MantineFieldComponent<SuppliedPillProps, P>;
52
53
  list<K extends keyof ListFieldsOfFields<F>>(valuePath: K): MantineFieldComponent<SuppliedListProps<`${K}.${number}`>, ComponentProps<typeof DefaultList<ElementOfArray<F[K]['value']>, K>>>;
53
- subForm<K extends keyof AllFieldsOfFields<F>, S extends SubFormFields<F, K>>(valuePath: K, SubForm: ComponentType<FormProps<S>>): ComponentType;
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>;
55
+ 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>;
54
56
  }
55
57
  export {};
@@ -4,17 +4,18 @@ 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';
8
+ import { createForm } from './create_form';
7
9
  import { createList, DefaultList, } from './create_list';
8
10
  import { createPill, } from './create_pill';
9
11
  import { createRadio, } from './create_radio';
10
12
  import { createRadioGroup, } from './create_radio_group';
11
- import { createSubForm } from './create_sub_form';
12
13
  import { createTextInput, } from './create_text_input';
13
14
  import { createValueInput, } from './create_value_input';
14
15
  function SimpleSelect(props) {
15
16
  return _jsx(Select, { ...props });
16
17
  }
17
- export function useMantineForm({ onFieldValueChange, onFieldBlur, onFieldFocus, onFieldSubmit, fields, }) {
18
+ export function useMantineFormFields({ onFieldValueChange, onFieldBlur, onFieldFocus, onFieldSubmit, fields, }) {
18
19
  const form = useMemo(function () {
19
20
  return new MantineFormImpl(fields);
20
21
  },
@@ -63,7 +64,8 @@ class MantineFormImpl {
63
64
  radioCache = new Cache(createRadio.bind(this));
64
65
  pillCache = new Cache(createPill.bind(this));
65
66
  listCache = new Cache(createList.bind(this));
66
- subFormCache = new Cache(createSubForm.bind(this));
67
+ fieldsViewCache = new Cache(createFieldsView.bind(this));
68
+ formCache = new Cache(createForm.bind(this));
67
69
  @observable.ref
68
70
  accessor fields;
69
71
  onFieldValueChange;
@@ -129,11 +131,19 @@ class MantineFormImpl {
129
131
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
130
132
  return this.listCache.retrieveOrCreate(valuePath, DefaultList);
131
133
  }
132
- // TODO have the returned component take any non-overlapping props as props
133
- subForm(valuePath, SubForm) {
134
- return this.subFormCache.retrieveOrCreate(valuePath,
134
+ fieldsView(valuePath, FieldsView) {
135
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
136
+ return this.fieldsViewCache.retrieveOrCreate(valuePath,
137
+ // strip props from component since we lose information in the cache
138
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
139
+ FieldsView, this);
140
+ }
141
+ form(valuePath, Form) {
142
+ // strip props from component since we lose information in the cache
143
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
144
+ return this.formCache.retrieveOrCreate(valuePath,
135
145
  // strip props from component since we lose information in the cache
136
146
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
137
- SubForm, this);
147
+ Form, this);
138
148
  }
139
149
  }
@@ -1,8 +1,8 @@
1
1
  import { type Meta, type StoryObj } from '@storybook/react';
2
- import { type FormProps } from 'core/props';
2
+ import { type FieldsViewProps } from 'core/props';
3
3
  import { type ErrorRenderer } from 'mantine/error_renderer';
4
4
  import { type Field } from 'types/field';
5
- declare function Component({ ErrorRenderer, ...props }: FormProps<{
5
+ declare function Component({ ErrorRenderer, ...props }: FieldsViewProps<{
6
6
  $: Field<boolean, string>;
7
7
  }> & {
8
8
  ErrorRenderer?: ErrorRenderer;
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { action } from '@storybook/addon-actions';
3
- import { useMantineForm } from 'mantine/hooks';
3
+ import { useMantineFormFields } from 'mantine/hooks';
4
4
  import { CHECKBOX_LABEL } from './checkbox_constants';
5
5
  function Component({ ErrorRenderer, ...props }) {
6
- const inputProps = useMantineForm(props);
6
+ const inputProps = useMantineFormFields(props);
7
7
  const CheckboxComponent = inputProps.checkbox('$');
8
8
  return (_jsx(CheckboxComponent, { ErrorRenderer: ErrorRenderer, label: CHECKBOX_LABEL }));
9
9
  }
@@ -1,7 +1,7 @@
1
1
  import { type Meta, type StoryObj } from '@storybook/react';
2
- import { type FormProps } from 'core/props';
2
+ import { type FieldsViewProps } from 'core/props';
3
3
  import { type Field } from 'types/field';
4
- declare function Component(props: FormProps<{
4
+ declare function Component(props: FieldsViewProps<{
5
5
  $: Field<string, string>;
6
6
  '$.a': Field<string, string>;
7
7
  }>): import("react/jsx-runtime").JSX.Element;
@@ -1,17 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Stack, } from '@mantine/core';
2
+ import { Button, Stack, } from '@mantine/core';
3
3
  import { action } from '@storybook/addon-actions';
4
- import { useMantineForm } from 'mantine/hooks';
5
- function SubFormImpl(props) {
6
- const form = useMantineForm(props);
4
+ import { useMantineFormFields } from 'mantine/hooks';
5
+ const onClick = action('some button clicked');
6
+ function SubFieldsView(props) {
7
+ const form = useMantineFormFields(props);
7
8
  const TextInput = form.textInput('$');
8
- return _jsx(TextInput, { label: 'sub form' });
9
+ return (_jsxs(Stack, { children: [_jsx(TextInput, { label: 'sub fields view' }), _jsx(Button, { onClick: props.onClick, children: "Bonus Button" })] }));
9
10
  }
10
11
  function Component(props) {
11
- const form = useMantineForm(props);
12
- const SubForm = form.subForm('$.a', SubFormImpl);
12
+ const form = useMantineFormFields(props);
13
+ const FieldsView = form.fieldsView('$.a', SubFieldsView);
13
14
  const TextInput = form.textInput('$');
14
- return (_jsxs(Stack, { children: [_jsx(TextInput, { label: 'form' }), _jsx(SubForm, {})] }));
15
+ return (_jsxs(Stack, { children: [_jsx(TextInput, { label: 'fields view' }), _jsx(FieldsView, { onClick: onClick })] }));
15
16
  }
16
17
  const meta = {
17
18
  component: Component,
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { composeStories } from '@storybook/react';
3
+ import { toArray } from '@strictly/base';
4
+ import { render, } from '@testing-library/react';
5
+ import * as stories from './fields_view_hooks.stories';
6
+ const composedStories = composeStories(stories);
7
+ describe('field view hooks', function () {
8
+ it.each(toArray(composedStories))('renders %s', function (_name, Story) {
9
+ const wrapper = render(_jsx(Story, {}));
10
+ expect(wrapper.container).toMatchSnapshot();
11
+ });
12
+ });
@@ -0,0 +1,12 @@
1
+ import { type Meta, type StoryObj } from '@storybook/react';
2
+ import { type FieldsViewProps } from 'core/props';
3
+ import { type Field } from 'types/field';
4
+ declare function Component(props: FieldsViewProps<{
5
+ $: Field<string>;
6
+ '$.a': Field<number>;
7
+ }>): import("react/jsx-runtime").JSX.Element;
8
+ declare const meta: Meta<typeof Component>;
9
+ export default meta;
10
+ type Story = StoryObj<typeof Component>;
11
+ export declare const Empty: Story;
12
+ export declare const Populated: Story;