@strictly/react-form 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.out/core/mobx/field_adapter_builder.js +18 -6
- package/.out/core/mobx/form_presenter.d.ts +5 -5
- package/.out/core/mobx/form_presenter.js +232 -127
- package/.out/core/mobx/hooks.d.ts +24 -4
- package/.out/core/mobx/hooks.js +26 -5
- package/.out/core/mobx/merge_field_adapters_with_validators.js +1 -5
- package/.out/core/mobx/specs/fixtures.js +2 -1
- package/.out/core/mobx/specs/form_presenter.tests.js +16 -8
- package/.out/core/mobx/specs/sub_form_field_adapters.tests.js +2 -1
- package/.out/core/mobx/sub_form_field_adapters.d.ts +2 -2
- package/.out/field_converters/integer_to_string_converter.js +12 -4
- package/.out/field_converters/maybe_identity_converter.js +12 -4
- package/.out/field_converters/nullable_to_boolean_converter.js +24 -7
- package/.out/field_converters/select_value_type_converter.js +36 -12
- package/.out/mantine/create_checkbox.js +8 -4
- package/.out/mantine/create_fields_view.d.ts +9 -1
- package/.out/mantine/create_fields_view.js +20 -5
- package/.out/mantine/create_form.js +1 -1
- package/.out/mantine/create_radio_group.js +8 -4
- package/.out/mantine/create_text_input.js +8 -4
- package/.out/mantine/create_value_input.js +8 -4
- package/.out/mantine/hooks.d.ts +2 -1
- package/.out/mantine/hooks.js +219 -93
- package/.out/mantine/specs/checkbox_hooks.stories.js +13 -1
- package/.out/mantine/specs/checkbox_hooks.tests.js +22 -9
- package/.out/mantine/specs/create_fields_view.tests.d.ts +1 -0
- package/.out/mantine/specs/create_fields_view.tests.js +17 -0
- package/.out/mantine/specs/fields_view_hooks.stories.d.ts +6 -2
- package/.out/mantine/specs/fields_view_hooks.stories.js +39 -7
- package/.out/mantine/specs/fields_view_hooks.tests.js +30 -1
- package/.out/mantine/specs/radio_group_hooks.stories.js +13 -1
- package/.out/mantine/specs/radio_group_hooks.tests.js +23 -10
- package/.out/mantine/specs/select_hooks.stories.js +13 -1
- package/.out/mantine/specs/text_input_hooks.stories.js +13 -1
- package/.out/mantine/specs/text_input_hooks.tests.js +18 -7
- package/.out/mantine/specs/value_input_hooks.stories.js +14 -2
- package/.out/tsconfig.tsbuildinfo +1 -1
- package/.out/tsup.config.js +2 -9
- package/.out/types/merge_validators.js +1 -4
- package/.out/util/partial.js +5 -5
- package/.out/vitest.workspace.js +2 -10
- package/.turbo/turbo-build.log +9 -9
- package/.turbo/turbo-check-types.log +1 -1
- package/.turbo/turbo-release$colon$exports.log +1 -1
- package/core/mobx/form_presenter.ts +15 -14
- package/core/mobx/hooks.tsx +197 -0
- package/core/mobx/specs/form_presenter.tests.ts +24 -5
- package/core/mobx/sub_form_field_adapters.ts +14 -3
- package/dist/index.cjs +395 -277
- package/dist/index.d.cts +52 -26
- package/dist/index.d.ts +52 -26
- package/dist/index.js +398 -276
- package/mantine/create_fields_view.tsx +66 -31
- package/mantine/hooks.tsx +9 -6
- package/mantine/specs/__snapshots__/fields_view_hooks.tests.tsx.snap +194 -197
- package/mantine/specs/create_fields_view.tests.ts +29 -0
- package/mantine/specs/fields_view_hooks.stories.tsx +58 -15
- package/mantine/specs/fields_view_hooks.tests.tsx +26 -0
- package/package.json +1 -1
- package/core/mobx/hooks.ts +0 -112
package/.out/tsup.config.js
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import { defineConfig, } from 'tsup';
|
|
2
|
-
export default defineConfig((options) => ({
|
|
3
|
-
entry: ['index.ts'],
|
|
4
|
-
tsconfig: './tsconfig.build.json',
|
|
5
|
-
clean: false,
|
|
6
|
-
dts: true,
|
|
7
|
-
format: [
|
|
2
|
+
export default defineConfig((options) => (Object.assign({ entry: ['index.ts'], tsconfig: './tsconfig.build.json', clean: false, dts: true, format: [
|
|
8
3
|
'cjs',
|
|
9
4
|
'esm',
|
|
10
|
-
],
|
|
11
|
-
...options,
|
|
12
|
-
}));
|
|
5
|
+
] }, options)));
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { annotations, validate, } from '@strictly/define';
|
|
2
2
|
export function mergeValidators(validators1, validators2) {
|
|
3
|
-
const validators = {
|
|
4
|
-
...validators1,
|
|
5
|
-
...validators2,
|
|
6
|
-
};
|
|
3
|
+
const validators = Object.assign(Object.assign({}, validators1), validators2);
|
|
7
4
|
const keys1 = new Set(Object.keys(validators1));
|
|
8
5
|
const keys2 = new Set(Object.keys(validators2));
|
|
9
6
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
package/.out/util/partial.js
CHANGED
|
@@ -8,7 +8,7 @@ export function createSimplePartialComponent(Component, curriedProps) {
|
|
|
8
8
|
// still needs a cast as `extends ComponentType<any>` != `ComponentType<any>`
|
|
9
9
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unnecessary-type-assertion
|
|
10
10
|
const C = Component;
|
|
11
|
-
return (_jsx(C, { ref: ref,
|
|
11
|
+
return (_jsx(C, Object.assign({ ref: ref }, curriedProps, exposedProps)));
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
14
|
export function createPartialComponent(Component, curriedPropsSource, additionalPropKeys = []) {
|
|
@@ -34,11 +34,11 @@ export function createPartialComponent(Component, curriedPropsSource, additional
|
|
|
34
34
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
35
35
|
{},
|
|
36
36
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
37
|
-
{
|
|
37
|
+
Object.assign({}, props),
|
|
38
38
|
]);
|
|
39
39
|
// TODO is there any way we can memoize this transformation?
|
|
40
40
|
const curriedProps = curriedPropsSource(additionalProps);
|
|
41
|
-
return (_jsx(C, { ref: ref,
|
|
41
|
+
return (_jsx(C, Object.assign({ ref: ref }, curriedProps, exposedProps)));
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
export function usePartialComponent(
|
|
@@ -88,11 +88,11 @@ export function createUnsafePartialObserverComponent(Component, curriedPropsSour
|
|
|
88
88
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
89
89
|
{},
|
|
90
90
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
91
|
-
{
|
|
91
|
+
Object.assign({}, props),
|
|
92
92
|
]);
|
|
93
93
|
// TODO is there any way we can memoize this transformation?
|
|
94
94
|
const curriedProps = curriedPropsSource(additionalProps);
|
|
95
|
-
return (_jsx(C, { ref: ref,
|
|
95
|
+
return (_jsx(C, Object.assign({ ref: ref }, curriedProps, exposedProps)));
|
|
96
96
|
}));
|
|
97
97
|
}
|
|
98
98
|
export function usePartialObserverComponent(
|
package/.out/vitest.workspace.js
CHANGED
|
@@ -4,19 +4,11 @@ import tsconfig from './tsconfig.json';
|
|
|
4
4
|
const config = createVitestUserConfig(tsconfig);
|
|
5
5
|
export default defineWorkspace([
|
|
6
6
|
'.',
|
|
7
|
-
{
|
|
8
|
-
...config,
|
|
9
|
-
extends: './.storybook/vite.config.mts',
|
|
10
|
-
test: {
|
|
11
|
-
...(config.test || {}),
|
|
12
|
-
environment: 'jsdom',
|
|
13
|
-
setupFiles: [
|
|
7
|
+
Object.assign(Object.assign({}, config), { extends: './.storybook/vite.config.mts', test: Object.assign(Object.assign({}, (config.test || {})), { environment: 'jsdom', setupFiles: [
|
|
14
8
|
'./.vitest/install_deterministic_random.ts',
|
|
15
9
|
// install storybook setup for unit tests that import stories directly
|
|
16
10
|
'./.vitest/install_storybook_preview.ts',
|
|
17
11
|
'./.vitest/match_media.ts',
|
|
18
12
|
'./.vitest/resize_observer.ts',
|
|
19
|
-
],
|
|
20
|
-
},
|
|
21
|
-
},
|
|
13
|
+
] }) }),
|
|
22
14
|
]);
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -4,15 +4,15 @@ $ tsup
|
|
|
4
4
|
[34mCLI[39m Using tsconfig: tsconfig.build.json
|
|
5
5
|
[34mCLI[39m tsup v8.3.5
|
|
6
6
|
[34mCLI[39m Using tsup config: /home/runner/work/strictly/strictly/packages/react-form/tsup.config.ts
|
|
7
|
-
[34mCLI[39m Target:
|
|
7
|
+
[34mCLI[39m Target: es6
|
|
8
8
|
[34mCJS[39m Build start
|
|
9
9
|
[34mESM[39m Build start
|
|
10
|
-
[
|
|
11
|
-
[
|
|
12
|
-
[
|
|
13
|
-
[
|
|
10
|
+
[32mESM[39m [1mdist/index.js [22m[32m56.04 KB[39m
|
|
11
|
+
[32mESM[39m ⚡️ Build success in 111ms
|
|
12
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m59.85 KB[39m
|
|
13
|
+
[32mCJS[39m ⚡️ Build success in 113ms
|
|
14
14
|
[34mDTS[39m Build start
|
|
15
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[
|
|
17
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
18
|
-
Done in
|
|
15
|
+
[32mDTS[39m ⚡️ Build success in 9897ms
|
|
16
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m39.16 KB[39m
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m39.16 KB[39m
|
|
18
|
+
Done in 10.95s.
|
|
@@ -102,7 +102,7 @@ export type ValuePathsToAdaptersOf<
|
|
|
102
102
|
}
|
|
103
103
|
: never
|
|
104
104
|
|
|
105
|
-
export class FormPresenter<
|
|
105
|
+
export abstract class FormPresenter<
|
|
106
106
|
T extends Type,
|
|
107
107
|
ValueToTypePaths extends Readonly<Record<string, string>>,
|
|
108
108
|
TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<
|
|
@@ -116,7 +116,7 @@ export class FormPresenter<
|
|
|
116
116
|
> {
|
|
117
117
|
constructor(
|
|
118
118
|
readonly type: T,
|
|
119
|
-
|
|
119
|
+
protected readonly adapters: TypePathsToAdapters,
|
|
120
120
|
) {
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -361,7 +361,7 @@ export class FormPresenter<
|
|
|
361
361
|
}
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
-
clearFieldValue<K extends StringKeyOf<
|
|
364
|
+
clearFieldValue<K extends StringKeyOf<ValueToTypePaths>>(
|
|
365
365
|
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
366
366
|
valuePath: K,
|
|
367
367
|
) {
|
|
@@ -375,8 +375,7 @@ export class FormPresenter<
|
|
|
375
375
|
convert,
|
|
376
376
|
create,
|
|
377
377
|
} = adapter
|
|
378
|
-
const
|
|
379
|
-
const value = accessor == null ? create(valuePath, model.value) : accessor.value
|
|
378
|
+
const value = create(valuePath, model.value)
|
|
380
379
|
const {
|
|
381
380
|
value: displayValue,
|
|
382
381
|
} = convert(value, valuePath, model.value)
|
|
@@ -412,6 +411,7 @@ export class FormPresenter<
|
|
|
412
411
|
validateField<K extends keyof ValuePathsToAdapters>(
|
|
413
412
|
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
414
413
|
valuePath: K,
|
|
414
|
+
ignoreDefaultValue = false,
|
|
415
415
|
): boolean {
|
|
416
416
|
const {
|
|
417
417
|
convert,
|
|
@@ -436,7 +436,14 @@ export class FormPresenter<
|
|
|
436
436
|
: storedValue
|
|
437
437
|
const dirty = storedValue !== value
|
|
438
438
|
assertExists(revert, 'changing field directly not supported {}', valuePath)
|
|
439
|
-
|
|
439
|
+
if (ignoreDefaultValue) {
|
|
440
|
+
const {
|
|
441
|
+
value: defaultDisplayValue,
|
|
442
|
+
} = convert(create(valuePath, model.value), valuePath, model.value)
|
|
443
|
+
if (defaultDisplayValue === value) {
|
|
444
|
+
return true
|
|
445
|
+
}
|
|
446
|
+
}
|
|
440
447
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
441
448
|
const conversion = revert(value, valuePath as string, model.value)
|
|
442
449
|
return runInAction(function () {
|
|
@@ -521,18 +528,12 @@ export class FormPresenter<
|
|
|
521
528
|
})
|
|
522
529
|
}
|
|
523
530
|
|
|
524
|
-
createModel(value: ValueOfType<ReadonlyTypeOfType<T>>): FormModel<
|
|
531
|
+
abstract createModel(value: ValueOfType<ReadonlyTypeOfType<T>>): FormModel<
|
|
525
532
|
T,
|
|
526
533
|
ValueToTypePaths,
|
|
527
534
|
TypePathsToAdapters,
|
|
528
535
|
ValuePathsToAdapters
|
|
529
|
-
>
|
|
530
|
-
return new FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>(
|
|
531
|
-
this.type,
|
|
532
|
-
value,
|
|
533
|
-
this.adapters,
|
|
534
|
-
)
|
|
535
|
-
}
|
|
536
|
+
>
|
|
536
537
|
}
|
|
537
538
|
|
|
538
539
|
export class FormModel<
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ReadonlyTypeOfType,
|
|
3
|
+
type ValueOfType,
|
|
4
|
+
} from '@strictly/define'
|
|
5
|
+
import {
|
|
6
|
+
type FieldsViewProps,
|
|
7
|
+
} from 'core/props'
|
|
8
|
+
import {
|
|
9
|
+
type ComponentType,
|
|
10
|
+
useCallback,
|
|
11
|
+
useMemo,
|
|
12
|
+
} from 'react'
|
|
13
|
+
import {
|
|
14
|
+
createUnsafePartialObserverComponent,
|
|
15
|
+
type UnsafePartialComponent,
|
|
16
|
+
} from 'util/partial'
|
|
17
|
+
import { type FormPresenter } from './form_presenter'
|
|
18
|
+
import {
|
|
19
|
+
type FormFieldsOfPresenter,
|
|
20
|
+
type ToValueOfPresenterValuePath,
|
|
21
|
+
type ValuePathsOfPresenter,
|
|
22
|
+
} from './types'
|
|
23
|
+
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
|
+
type ValueOfPresenter<P extends FormPresenter<any, any, any, any>> = P extends FormPresenter<infer T, any, any, any>
|
|
26
|
+
? ValueOfType<ReadonlyTypeOfType<T>>
|
|
27
|
+
: never
|
|
28
|
+
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
+
type ModelOfPresenter<P extends FormPresenter<any, any, any, any>> = ReturnType<P['createModel']>
|
|
31
|
+
|
|
32
|
+
export function useDefaultMobxFormHooks<
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
+
P extends FormPresenter<any, any, any, any>,
|
|
35
|
+
C extends ComponentType<FieldsViewProps<F>>,
|
|
36
|
+
F extends FormFieldsOfPresenter<P> = FormFieldsOfPresenter<P>,
|
|
37
|
+
>(
|
|
38
|
+
presenter: P,
|
|
39
|
+
value: ValueOfPresenter<P>,
|
|
40
|
+
options?: {
|
|
41
|
+
onValidFieldSubmit?: <Path extends ValuePathsOfPresenter<P>>(model: ModelOfPresenter<P>, valuePath: Path) => void,
|
|
42
|
+
onValidFormSubmit?: (model: ModelOfPresenter<P>, value: ValueOfPresenter<P>) => void,
|
|
43
|
+
},
|
|
44
|
+
): {
|
|
45
|
+
model: ModelOfPresenter<P>,
|
|
46
|
+
FormFields?: UnsafePartialComponent<C, FieldsViewProps<F>>,
|
|
47
|
+
onFormSubmit: () => void,
|
|
48
|
+
onFieldValueChange<K extends keyof F>(this: void, key: K, value: F[K]['value']): void,
|
|
49
|
+
onFieldFocus?(this: void, key: keyof F): void,
|
|
50
|
+
onFieldBlur?(this: void, key: keyof F): void,
|
|
51
|
+
onFieldSubmit?(this: void, key: keyof F): boolean | void,
|
|
52
|
+
}
|
|
53
|
+
export function useDefaultMobxFormHooks<
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
P extends FormPresenter<any, any, any, any>,
|
|
56
|
+
C extends ComponentType<FieldsViewProps<F>>,
|
|
57
|
+
F extends FormFieldsOfPresenter<P> = FormFieldsOfPresenter<P>,
|
|
58
|
+
>(
|
|
59
|
+
presenter: P,
|
|
60
|
+
value: ValueOfPresenter<P>,
|
|
61
|
+
options: {
|
|
62
|
+
onValidFieldSubmit?: <Path extends ValuePathsOfPresenter<P>>(model: ModelOfPresenter<P>, valuePath: Path) => void,
|
|
63
|
+
onValidFormSubmit?: (model: ModelOfPresenter<P>, value: ValueOfPresenter<P>) => void,
|
|
64
|
+
// TODO the types of C are not working, needs a local implementation to test
|
|
65
|
+
FormFieldsView: C,
|
|
66
|
+
},
|
|
67
|
+
): {
|
|
68
|
+
model: ModelOfPresenter<P>,
|
|
69
|
+
FormFields: UnsafePartialComponent<C, FieldsViewProps<F>>,
|
|
70
|
+
onFormSubmit: () => void,
|
|
71
|
+
onFieldValueChange<K extends keyof F>(this: void, key: K, value: F[K]['value']): void,
|
|
72
|
+
onFieldFocus?(this: void, key: keyof F): void,
|
|
73
|
+
onFieldBlur?(this: void, key: keyof F): void,
|
|
74
|
+
onFieldSubmit?(this: void, key: keyof F): boolean | void,
|
|
75
|
+
}
|
|
76
|
+
export function useDefaultMobxFormHooks<
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
+
P extends FormPresenter<any, any, any, any>,
|
|
79
|
+
C extends FieldsViewProps<F>,
|
|
80
|
+
F extends FormFieldsOfPresenter<P> = FormFieldsOfPresenter<P>,
|
|
81
|
+
>(
|
|
82
|
+
presenter: P,
|
|
83
|
+
value: ValueOfPresenter<P>,
|
|
84
|
+
{
|
|
85
|
+
onValidFieldSubmit,
|
|
86
|
+
onValidFormSubmit,
|
|
87
|
+
FormFieldsView,
|
|
88
|
+
}: {
|
|
89
|
+
onValidFieldSubmit?: <Path extends ValuePathsOfPresenter<P>>(model: ModelOfPresenter<P>, valuePath: Path) => void,
|
|
90
|
+
onValidFormSubmit?: (model: ModelOfPresenter<P>, value: ValueOfPresenter<P>) => void,
|
|
91
|
+
FormFieldsView?: ComponentType<C>,
|
|
92
|
+
} = {},
|
|
93
|
+
): {
|
|
94
|
+
model: ModelOfPresenter<P>,
|
|
95
|
+
FormFields?: UnsafePartialComponent<ComponentType<C>, FieldsViewProps<F>> | undefined,
|
|
96
|
+
onFormSubmit: () => void,
|
|
97
|
+
onFieldValueChange<K extends keyof F>(this: void, key: K, value: F[K]['value']): void,
|
|
98
|
+
onFieldFocus?(this: void, key: keyof F): void,
|
|
99
|
+
onFieldBlur?(this: void, key: keyof F): void,
|
|
100
|
+
onFieldSubmit?(this: void, key: keyof F): boolean | void,
|
|
101
|
+
} {
|
|
102
|
+
const model = useMemo(function () {
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
104
|
+
return presenter.createModel(value) as ReturnType<P['createModel']>
|
|
105
|
+
}, [
|
|
106
|
+
presenter,
|
|
107
|
+
value,
|
|
108
|
+
])
|
|
109
|
+
|
|
110
|
+
const onFieldValueChange = useCallback(
|
|
111
|
+
function<Path extends ValuePathsOfPresenter<P>> (
|
|
112
|
+
path: Path,
|
|
113
|
+
value: ToValueOfPresenterValuePath<P, Path>,
|
|
114
|
+
) {
|
|
115
|
+
presenter.clearFieldError(model, path)
|
|
116
|
+
presenter.setFieldValue<Path>(model, path, value)
|
|
117
|
+
},
|
|
118
|
+
[
|
|
119
|
+
presenter,
|
|
120
|
+
model,
|
|
121
|
+
],
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
const onFieldSubmit = useCallback(
|
|
125
|
+
function<Path extends ValuePathsOfPresenter<P>> (valuePath: Path) {
|
|
126
|
+
if (presenter.validateField(model, valuePath)) {
|
|
127
|
+
onValidFieldSubmit?.(model, valuePath)
|
|
128
|
+
}
|
|
129
|
+
return false
|
|
130
|
+
},
|
|
131
|
+
[
|
|
132
|
+
presenter,
|
|
133
|
+
model,
|
|
134
|
+
onValidFieldSubmit,
|
|
135
|
+
],
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
const onFieldBlur = useCallback(
|
|
139
|
+
function<Path extends ValuePathsOfPresenter<P>> (path: Path) {
|
|
140
|
+
// work around potential loss of focus prior to state potentially invalidating change triggering
|
|
141
|
+
// (e.g. changing a discriminator)
|
|
142
|
+
// TODO debounce?
|
|
143
|
+
setTimeout(function () {
|
|
144
|
+
if (presenter.isValuePathActive(model, path)) {
|
|
145
|
+
presenter.validateField(model, path, true)
|
|
146
|
+
}
|
|
147
|
+
}, 100)
|
|
148
|
+
},
|
|
149
|
+
[
|
|
150
|
+
presenter,
|
|
151
|
+
model,
|
|
152
|
+
],
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
const onFormSubmit = useCallback(
|
|
156
|
+
function () {
|
|
157
|
+
if (presenter.validateAll(model)) {
|
|
158
|
+
onValidFormSubmit?.(model, model.value)
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
[
|
|
162
|
+
presenter,
|
|
163
|
+
model,
|
|
164
|
+
onValidFormSubmit,
|
|
165
|
+
],
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const FormFields = useMemo(() => {
|
|
169
|
+
if (FormFieldsView == null) {
|
|
170
|
+
return undefined
|
|
171
|
+
}
|
|
172
|
+
return createUnsafePartialObserverComponent(FormFieldsView, (): FieldsViewProps<F> => {
|
|
173
|
+
return {
|
|
174
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
175
|
+
fields: model.fields as C['fields'],
|
|
176
|
+
onFieldBlur,
|
|
177
|
+
onFieldSubmit,
|
|
178
|
+
onFieldValueChange,
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
}, [
|
|
182
|
+
model,
|
|
183
|
+
FormFieldsView,
|
|
184
|
+
onFieldBlur,
|
|
185
|
+
onFieldSubmit,
|
|
186
|
+
onFieldValueChange,
|
|
187
|
+
])
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
model,
|
|
191
|
+
onFieldValueChange,
|
|
192
|
+
onFieldSubmit,
|
|
193
|
+
onFieldBlur,
|
|
194
|
+
onFormSubmit,
|
|
195
|
+
FormFields,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -6,10 +6,14 @@ import {
|
|
|
6
6
|
nullType,
|
|
7
7
|
numberType,
|
|
8
8
|
object,
|
|
9
|
+
type ReadonlyOfTypeDef,
|
|
10
|
+
type ReadonlyTypeOfType,
|
|
9
11
|
record,
|
|
10
12
|
stringType,
|
|
13
|
+
type Type,
|
|
11
14
|
union,
|
|
12
15
|
type ValueOfType,
|
|
16
|
+
type ValueOfTypeDef,
|
|
13
17
|
type ValueToTypePathsOfType,
|
|
14
18
|
} from '@strictly/define'
|
|
15
19
|
import { type FieldAdapter } from 'core/mobx/field_adapter'
|
|
@@ -39,6 +43,21 @@ import {
|
|
|
39
43
|
|
|
40
44
|
const IS_NAN_ERROR = 1
|
|
41
45
|
|
|
46
|
+
class TestFormPresenter<
|
|
47
|
+
T extends Type,
|
|
48
|
+
ValueToTypePaths extends Readonly<Record<string, string>>,
|
|
49
|
+
TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<
|
|
50
|
+
FlattenedValuesOfType<T, '*'>,
|
|
51
|
+
ValueOfType<ReadonlyTypeOfType<T>>
|
|
52
|
+
>,
|
|
53
|
+
> extends FormPresenter<T, ValueToTypePaths, TypePathsToAdapters> {
|
|
54
|
+
override createModel(value: ValueOfTypeDef<ReadonlyOfTypeDef<T['definition']>, {}>): FormModel<T, ValueToTypePaths,
|
|
55
|
+
TypePathsToAdapters, ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths>>
|
|
56
|
+
{
|
|
57
|
+
return new FormModel(this.type, value, this.adapters)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
42
61
|
const originalIntegerToStringAdapter = adapterFromTwoWayConverter(
|
|
43
62
|
new IntegerToStringConverter(IS_NAN_ERROR),
|
|
44
63
|
prototypingFieldValueFactory(0),
|
|
@@ -479,7 +498,7 @@ describe('all', function () {
|
|
|
479
498
|
const adapters = {
|
|
480
499
|
$: integerToStringAdapter,
|
|
481
500
|
} as const
|
|
482
|
-
const presenter = new
|
|
501
|
+
const presenter = new TestFormPresenter<
|
|
483
502
|
typeof typeDef,
|
|
484
503
|
ValueToTypePathsOfType<typeof typeDef>,
|
|
485
504
|
typeof adapters
|
|
@@ -600,7 +619,7 @@ describe('all', function () {
|
|
|
600
619
|
const converters = {
|
|
601
620
|
'$.*': integerToStringAdapter,
|
|
602
621
|
} as const
|
|
603
|
-
const presenter = new
|
|
622
|
+
const presenter = new TestFormPresenter<
|
|
604
623
|
typeof typeDef,
|
|
605
624
|
ValueToTypePathsOfType<typeof typeDef>,
|
|
606
625
|
typeof converters
|
|
@@ -927,7 +946,7 @@ describe('all', function () {
|
|
|
927
946
|
'$.*': integerToStringAdapter,
|
|
928
947
|
} as const
|
|
929
948
|
type ValueToTypePaths = ValueToTypePathsOfType<typeof type>
|
|
930
|
-
const presenter = new
|
|
949
|
+
const presenter = new TestFormPresenter<
|
|
931
950
|
typeof type,
|
|
932
951
|
ValueToTypePaths,
|
|
933
952
|
typeof adapters
|
|
@@ -997,7 +1016,7 @@ describe('all', function () {
|
|
|
997
1016
|
'$.x:a': identityAdapter(0).narrow,
|
|
998
1017
|
'$.y:b': identityAdapter(false).narrow,
|
|
999
1018
|
} as const
|
|
1000
|
-
const presenter = new
|
|
1019
|
+
const presenter = new TestFormPresenter<
|
|
1001
1020
|
typeof type,
|
|
1002
1021
|
ValueToTypePaths,
|
|
1003
1022
|
typeof adapters
|
|
@@ -1068,7 +1087,7 @@ describe('all', function () {
|
|
|
1068
1087
|
$: '$',
|
|
1069
1088
|
'$.fake': '$.fake',
|
|
1070
1089
|
}
|
|
1071
|
-
const presenter = new
|
|
1090
|
+
const presenter = new TestFormPresenter<
|
|
1072
1091
|
typeof typeDef,
|
|
1073
1092
|
JsonPaths,
|
|
1074
1093
|
typeof converters
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type StringConcatOf } from '@strictly/base'
|
|
2
2
|
import {
|
|
3
3
|
flattenValuesOfType,
|
|
4
|
+
type ReadonlyTypeOfType,
|
|
4
5
|
type Type,
|
|
5
6
|
type ValueOfType,
|
|
6
7
|
} from '@strictly/define'
|
|
@@ -47,10 +48,15 @@ export function subFormFieldAdapters<
|
|
|
47
48
|
subAdapters: SubAdapters,
|
|
48
49
|
parentTypePath: TypePath,
|
|
49
50
|
contextType: ContextType,
|
|
50
|
-
): SubFormFieldAdapters<
|
|
51
|
+
): SubFormFieldAdapters<
|
|
52
|
+
SubAdapters,
|
|
53
|
+
TypePath,
|
|
54
|
+
TypePathsToValuePaths[TypePath],
|
|
55
|
+
ValueOfType<ReadonlyTypeOfType<ContextType>>
|
|
56
|
+
> {
|
|
51
57
|
// assume the number of '.' in the type path will correspond to the number of '.' in the value path
|
|
52
58
|
const dotCount = parentTypePath.split('.').length
|
|
53
|
-
function getSubValuePathAndContext(valuePath: string, context: ValueOfType<ContextType
|
|
59
|
+
function getSubValuePathAndContext(valuePath: string, context: ValueOfType<ReadonlyTypeOfType<ContextType>>) {
|
|
54
60
|
const parentValuePath = valuePath.split('.').slice(0, dotCount).join('.')
|
|
55
61
|
const subValuePath = valuePath.replace(parentValuePath, '$')
|
|
56
62
|
const subContext = flattenValuesOfType(contextType, context)[parentValuePath]
|
|
@@ -80,5 +86,10 @@ export function subFormFieldAdapters<
|
|
|
80
86
|
}
|
|
81
87
|
acc[typePath] = adaptedAdapter
|
|
82
88
|
return acc
|
|
83
|
-
}, {}) as SubFormFieldAdapters<
|
|
89
|
+
}, {}) as SubFormFieldAdapters<
|
|
90
|
+
SubAdapters,
|
|
91
|
+
TypePath,
|
|
92
|
+
TypePathsToValuePaths[TypePath],
|
|
93
|
+
ValueOfType<ReadonlyTypeOfType<ContextType>>
|
|
94
|
+
>
|
|
84
95
|
}
|