@strictly/react-form 0.0.3 → 0.0.5
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/.storybook/main.js +3 -1
- package/.out/core/mobx/field_adapter_builder.d.ts +1 -1
- package/.out/core/mobx/field_adapter_builder.js +1 -2
- package/.out/core/mobx/specs/sub_form_field_adapters.tests.d.ts +1 -0
- package/.out/core/mobx/specs/sub_form_field_adapters.tests.js +41 -0
- package/.out/core/mobx/sub_form_field_adapters.d.ts +7 -0
- package/.out/core/mobx/sub_form_field_adapters.js +8 -0
- package/.out/index.d.ts +1 -0
- package/.out/index.js +1 -0
- package/.out/mantine/create_list.d.ts +5 -4
- package/.out/mantine/create_list.js +4 -2
- package/.out/mantine/create_sub_form.d.ts +6 -0
- package/.out/mantine/create_sub_form.js +40 -0
- package/.out/mantine/hooks.d.ts +5 -2
- package/.out/mantine/hooks.js +9 -0
- package/.out/mantine/specs/list_hooks.stories.js +6 -6
- package/.out/mantine/specs/sub_form_hooks.stories.d.ts +15 -0
- package/.out/mantine/specs/sub_form_hooks.stories.js +107 -0
- package/.out/tsconfig.tsbuildinfo +1 -1
- package/.out/types/specs/list_fields_of_fields.tests.d.ts +1 -0
- package/.out/types/specs/list_fields_of_fields.tests.js +12 -0
- package/.out/types/specs/sub_form_fields.tests.d.ts +1 -0
- package/.out/types/specs/sub_form_fields.tests.js +12 -0
- package/.out/types/sub_form_fields.d.ts +7 -0
- package/.out/types/sub_form_fields.js +1 -0
- package/.storybook/main.ts +3 -1
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-check-types.log +1 -1
- package/.turbo/turbo-release$colon$exports.log +1 -1
- package/core/mobx/field_adapter_builder.ts +3 -4
- package/core/mobx/specs/sub_form_field_adapters.tests.ts +59 -0
- package/core/mobx/sub_form_field_adapters.ts +21 -0
- package/dist/index.cjs +86 -24
- package/dist/index.d.cts +22 -8
- package/dist/index.d.ts +22 -8
- package/dist/index.js +85 -24
- package/index.ts +1 -0
- package/mantine/create_list.tsx +10 -4
- package/mantine/create_sub_form.tsx +70 -0
- package/mantine/hooks.tsx +29 -5
- package/mantine/specs/__snapshots__/list_hooks.tests.tsx.snap +56 -8
- package/mantine/specs/list_hooks.stories.tsx +16 -6
- package/mantine/specs/sub_form_hooks.stories.tsx +135 -0
- package/package.json +1 -1
- package/types/specs/list_fields_of_fields.tests.ts +29 -0
- package/types/specs/sub_form_fields.tests.ts +22 -0
- package/types/sub_form_fields.ts +7 -0
- package/.out/field_converters/list_converter.d.ts +0 -2
- package/.out/field_converters/list_converter.js +0 -13
- package/field_converters/list_converter.ts +0 -20
package/dist/index.js
CHANGED
|
@@ -87,20 +87,6 @@ function unreliableIdentityConverter() {
|
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
// field_converters/list_converter.ts
|
|
91
|
-
function listConverter() {
|
|
92
|
-
return function(from, valuePath) {
|
|
93
|
-
const value = from.map(function(_v, i) {
|
|
94
|
-
return `${valuePath}.${i}`;
|
|
95
|
-
});
|
|
96
|
-
return {
|
|
97
|
-
value,
|
|
98
|
-
required: false,
|
|
99
|
-
readonly: false
|
|
100
|
-
};
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
90
|
// field_converters/maybe_identity_converter.ts
|
|
105
91
|
var MaybeIdentityConverter = class {
|
|
106
92
|
constructor(converter, isFrom) {
|
|
@@ -189,8 +175,9 @@ function identityAdapter(prototype, required) {
|
|
|
189
175
|
}
|
|
190
176
|
function listAdapter() {
|
|
191
177
|
return new FieldAdapterBuilder(
|
|
192
|
-
|
|
193
|
-
prototypingFieldValueFactory([])
|
|
178
|
+
annotatedIdentityConverter(false),
|
|
179
|
+
prototypingFieldValueFactory([]),
|
|
180
|
+
unreliableIdentityConverter()
|
|
194
181
|
);
|
|
195
182
|
}
|
|
196
183
|
|
|
@@ -746,6 +733,18 @@ function mergeAdaptersWithValidators(adapters, validators) {
|
|
|
746
733
|
);
|
|
747
734
|
}
|
|
748
735
|
|
|
736
|
+
// core/mobx/sub_form_field_adapters.ts
|
|
737
|
+
function subFormFieldAdapters(subAdapters, prefix) {
|
|
738
|
+
return Object.entries(subAdapters).reduce((acc, [
|
|
739
|
+
subKey,
|
|
740
|
+
subValue
|
|
741
|
+
]) => {
|
|
742
|
+
const key = subKey.replace("$", prefix);
|
|
743
|
+
acc[key] = subValue;
|
|
744
|
+
return acc;
|
|
745
|
+
}, {});
|
|
746
|
+
}
|
|
747
|
+
|
|
749
748
|
// field_converters/integer_to_string_converter.ts
|
|
750
749
|
var IntegerToStringConverter = class {
|
|
751
750
|
constructor(isNanError, base = 10) {
|
|
@@ -1194,17 +1193,20 @@ function createList(valuePath, List) {
|
|
|
1194
1193
|
const propSource = () => {
|
|
1195
1194
|
const values = [...this.fields[valuePath].value];
|
|
1196
1195
|
return {
|
|
1197
|
-
values
|
|
1196
|
+
values,
|
|
1197
|
+
listPath: valuePath
|
|
1198
1198
|
};
|
|
1199
1199
|
};
|
|
1200
1200
|
return createUnsafePartialObserverComponent(List, propSource);
|
|
1201
1201
|
}
|
|
1202
1202
|
function DefaultList({
|
|
1203
1203
|
values,
|
|
1204
|
+
listPath,
|
|
1204
1205
|
children
|
|
1205
1206
|
}) {
|
|
1206
1207
|
return /* @__PURE__ */ jsx3(Fragment, { children: values.map(function(value, index) {
|
|
1207
|
-
|
|
1208
|
+
const valuePath = `${listPath}.${index}`;
|
|
1209
|
+
return children(valuePath, value, index);
|
|
1208
1210
|
}) });
|
|
1209
1211
|
}
|
|
1210
1212
|
|
|
@@ -1279,8 +1281,53 @@ function createRadioGroup(valuePath, RadioGroup) {
|
|
|
1279
1281
|
return createUnsafePartialObserverComponent(RadioGroup, propSource, ["ErrorRenderer"]);
|
|
1280
1282
|
}
|
|
1281
1283
|
|
|
1282
|
-
// mantine/
|
|
1284
|
+
// mantine/create_sub_form.tsx
|
|
1285
|
+
import { observer as observer2 } from "mobx-react";
|
|
1283
1286
|
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
1287
|
+
function createSubForm(valuePath, SubForm, observableProps) {
|
|
1288
|
+
function toKey(subKey) {
|
|
1289
|
+
return subKey.replace("$", valuePath);
|
|
1290
|
+
}
|
|
1291
|
+
function toSubKey(key) {
|
|
1292
|
+
return key.replace(valuePath, "$");
|
|
1293
|
+
}
|
|
1294
|
+
function onFieldValueChange(subKey, value) {
|
|
1295
|
+
observableProps.onFieldValueChange(toKey(subKey), value);
|
|
1296
|
+
}
|
|
1297
|
+
function onFieldBlur(subKey) {
|
|
1298
|
+
observableProps.onFieldBlur?.(toKey(subKey));
|
|
1299
|
+
}
|
|
1300
|
+
function onFieldFocus(subKey) {
|
|
1301
|
+
observableProps.onFieldFocus?.(toKey(subKey));
|
|
1302
|
+
}
|
|
1303
|
+
function onFieldSubmit(subKey) {
|
|
1304
|
+
observableProps.onFieldSubmit?.(toKey(subKey));
|
|
1305
|
+
}
|
|
1306
|
+
return observer2(function() {
|
|
1307
|
+
const subFields = Object.entries(observableProps.fields).reduce((acc, [
|
|
1308
|
+
fieldKey,
|
|
1309
|
+
fieldValue
|
|
1310
|
+
]) => {
|
|
1311
|
+
if (fieldKey.startsWith(valuePath)) {
|
|
1312
|
+
acc[toSubKey(fieldKey)] = fieldValue;
|
|
1313
|
+
}
|
|
1314
|
+
return acc;
|
|
1315
|
+
}, {});
|
|
1316
|
+
return /* @__PURE__ */ jsx5(
|
|
1317
|
+
SubForm,
|
|
1318
|
+
{
|
|
1319
|
+
fields: subFields,
|
|
1320
|
+
onFieldBlur,
|
|
1321
|
+
onFieldFocus,
|
|
1322
|
+
onFieldSubmit,
|
|
1323
|
+
onFieldValueChange
|
|
1324
|
+
}
|
|
1325
|
+
);
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// mantine/create_text_input.tsx
|
|
1330
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
1284
1331
|
function createTextInput(valuePath, TextInput) {
|
|
1285
1332
|
const onChange = (e) => {
|
|
1286
1333
|
this.onFieldValueChange?.(valuePath, e.target.value);
|
|
@@ -1314,7 +1361,7 @@ function createTextInput(valuePath, TextInput) {
|
|
|
1314
1361
|
value,
|
|
1315
1362
|
disabled: readonly,
|
|
1316
1363
|
required,
|
|
1317
|
-
error: error && /* @__PURE__ */
|
|
1364
|
+
error: error && /* @__PURE__ */ jsx6(ErrorRenderer, { error }),
|
|
1318
1365
|
onChange,
|
|
1319
1366
|
onFocus,
|
|
1320
1367
|
onBlur,
|
|
@@ -1329,7 +1376,7 @@ function createTextInput(valuePath, TextInput) {
|
|
|
1329
1376
|
}
|
|
1330
1377
|
|
|
1331
1378
|
// mantine/create_value_input.tsx
|
|
1332
|
-
import { jsx as
|
|
1379
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1333
1380
|
function createValueInput(valuePath, ValueInput) {
|
|
1334
1381
|
const onChange = (value) => {
|
|
1335
1382
|
this.onFieldValueChange?.(valuePath, value);
|
|
@@ -1363,7 +1410,7 @@ function createValueInput(valuePath, ValueInput) {
|
|
|
1363
1410
|
value,
|
|
1364
1411
|
disabled: readonly,
|
|
1365
1412
|
required,
|
|
1366
|
-
error: error && /* @__PURE__ */
|
|
1413
|
+
error: error && /* @__PURE__ */ jsx7(ErrorRenderer, { error }),
|
|
1367
1414
|
onChange,
|
|
1368
1415
|
onFocus,
|
|
1369
1416
|
onBlur,
|
|
@@ -1378,9 +1425,9 @@ function createValueInput(valuePath, ValueInput) {
|
|
|
1378
1425
|
}
|
|
1379
1426
|
|
|
1380
1427
|
// mantine/hooks.tsx
|
|
1381
|
-
import { jsx as
|
|
1428
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1382
1429
|
function SimpleSelect(props) {
|
|
1383
|
-
return /* @__PURE__ */
|
|
1430
|
+
return /* @__PURE__ */ jsx8(Select, { ...props });
|
|
1384
1431
|
}
|
|
1385
1432
|
function useMantineForm({
|
|
1386
1433
|
onFieldValueChange,
|
|
@@ -1453,6 +1500,9 @@ var MantineFormImpl = class {
|
|
|
1453
1500
|
listCache = new Cache(
|
|
1454
1501
|
createList.bind(this)
|
|
1455
1502
|
);
|
|
1503
|
+
subFormCache = new Cache(
|
|
1504
|
+
createSubForm.bind(this)
|
|
1505
|
+
);
|
|
1456
1506
|
@observable2.ref
|
|
1457
1507
|
accessor fields;
|
|
1458
1508
|
onFieldValueChange;
|
|
@@ -1518,6 +1568,16 @@ var MantineFormImpl = class {
|
|
|
1518
1568
|
DefaultList
|
|
1519
1569
|
);
|
|
1520
1570
|
}
|
|
1571
|
+
// TODO have the returned component take any non-overlapping props as props
|
|
1572
|
+
subForm(valuePath, SubForm) {
|
|
1573
|
+
return this.subFormCache.retrieveOrCreate(
|
|
1574
|
+
valuePath,
|
|
1575
|
+
// strip props from component since we lose information in the cache
|
|
1576
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
1577
|
+
SubForm,
|
|
1578
|
+
this
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1521
1581
|
};
|
|
1522
1582
|
|
|
1523
1583
|
// types/merge_validators.ts
|
|
@@ -1586,6 +1646,7 @@ export {
|
|
|
1586
1646
|
mergeFieldAdaptersWithTwoWayConverter,
|
|
1587
1647
|
mergeValidators,
|
|
1588
1648
|
prototypingFieldValueFactory,
|
|
1649
|
+
subFormFieldAdapters,
|
|
1589
1650
|
useMantineForm,
|
|
1590
1651
|
usePartialComponent,
|
|
1591
1652
|
usePartialObserverComponent,
|
package/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from './core/mobx/form_fields_of_field_adapters'
|
|
|
6
6
|
export * from './core/mobx/form_presenter'
|
|
7
7
|
export * from './core/mobx/merge_field_adapters_with_two_way_converter'
|
|
8
8
|
export * from './core/mobx/merge_field_adapters_with_validators'
|
|
9
|
+
export * from './core/mobx/sub_form_field_adapters'
|
|
9
10
|
export * from './core/mobx/types'
|
|
10
11
|
export * from './core/props'
|
|
11
12
|
export * from './field_converters/integer_to_string_converter'
|
package/mantine/create_list.tsx
CHANGED
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
} from './types'
|
|
11
11
|
|
|
12
12
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
-
export type SuppliedListProps<Value = any> = {
|
|
13
|
+
export type SuppliedListProps<Value = any, ListPath extends string = string> = {
|
|
14
14
|
values: readonly Value[],
|
|
15
|
+
listPath: ListPath,
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export function createList<
|
|
@@ -19,6 +20,7 @@ export function createList<
|
|
|
19
20
|
K extends keyof ListFieldsOfFields<F>,
|
|
20
21
|
Props extends SuppliedListProps<ElementOfArray<ValueTypeOfField<F[K]>>> & {
|
|
21
22
|
children: (
|
|
23
|
+
valuePath: `${K}.${number}`,
|
|
22
24
|
value: ElementOfArray<ValueTypeOfField<F[K]>>,
|
|
23
25
|
index: number,
|
|
24
26
|
) => React.ReactNode,
|
|
@@ -32,6 +34,7 @@ export function createList<
|
|
|
32
34
|
const values = [...this.fields[valuePath].value]
|
|
33
35
|
return {
|
|
34
36
|
values,
|
|
37
|
+
listPath: valuePath,
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
return createUnsafePartialObserverComponent(List, propSource)
|
|
@@ -39,16 +42,19 @@ export function createList<
|
|
|
39
42
|
|
|
40
43
|
export function DefaultList<
|
|
41
44
|
Value,
|
|
45
|
+
ListPath extends string,
|
|
42
46
|
>({
|
|
43
47
|
values,
|
|
48
|
+
listPath,
|
|
44
49
|
children,
|
|
45
|
-
}: SuppliedListProps<Value> & {
|
|
46
|
-
children: (value: Value, index: number) => React.ReactNode,
|
|
50
|
+
}: SuppliedListProps<Value, ListPath> & {
|
|
51
|
+
children: (valuePath: `${ListPath}.${number}`, value: Value, index: number) => React.ReactNode,
|
|
47
52
|
}) {
|
|
48
53
|
return (
|
|
49
54
|
<>
|
|
50
55
|
{values.map(function (value, index) {
|
|
51
|
-
|
|
56
|
+
const valuePath: `${ListPath}.${number}` = `${listPath}.${index}`
|
|
57
|
+
return children(valuePath, value, index)
|
|
52
58
|
})}
|
|
53
59
|
</>
|
|
54
60
|
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { FormProps } from 'core/props'
|
|
2
|
+
import { observer } from 'mobx-react'
|
|
3
|
+
import type { ComponentType } from 'react'
|
|
4
|
+
import type { AllFieldsOfFields } from 'types/all_fields_of_fields'
|
|
5
|
+
import type { Fields } from 'types/field'
|
|
6
|
+
import type { SubFormFields } from 'types/sub_form_fields'
|
|
7
|
+
import type { ValueTypeOfField } from 'types/value_type_of_field'
|
|
8
|
+
|
|
9
|
+
export function createSubForm<
|
|
10
|
+
F extends Fields,
|
|
11
|
+
K extends keyof AllFieldsOfFields<F>,
|
|
12
|
+
S extends Fields = SubFormFields<F, K>,
|
|
13
|
+
>(
|
|
14
|
+
valuePath: K,
|
|
15
|
+
SubForm: ComponentType<FormProps<S>>,
|
|
16
|
+
observableProps: FormProps<F>,
|
|
17
|
+
) {
|
|
18
|
+
function toKey(subKey: string | number | symbol): string {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
20
|
+
return (subKey as string).replace('$', valuePath as string)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function toSubKey(key: string | number | symbol): string {
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
25
|
+
return (key as string).replace(valuePath as string, '$')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function onFieldValueChange<SubK extends keyof S>(
|
|
29
|
+
subKey: SubK,
|
|
30
|
+
value: ValueTypeOfField<S[SubK]>,
|
|
31
|
+
) {
|
|
32
|
+
// convert from subKey to key
|
|
33
|
+
observableProps.onFieldValueChange(toKey(subKey), value)
|
|
34
|
+
}
|
|
35
|
+
function onFieldBlur(subKey: keyof S) {
|
|
36
|
+
observableProps.onFieldBlur?.(toKey(subKey))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function onFieldFocus(subKey: keyof S) {
|
|
40
|
+
observableProps.onFieldFocus?.(toKey(subKey))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function onFieldSubmit(subKey: keyof S) {
|
|
44
|
+
observableProps.onFieldSubmit?.(toKey(subKey))
|
|
45
|
+
}
|
|
46
|
+
return observer(function () {
|
|
47
|
+
// convert fields to sub-fields
|
|
48
|
+
const subFields = Object.entries(observableProps.fields).reduce<Record<string, unknown>>((acc, [
|
|
49
|
+
fieldKey,
|
|
50
|
+
fieldValue,
|
|
51
|
+
]) => {
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
53
|
+
if (fieldKey.startsWith(valuePath as string)) {
|
|
54
|
+
acc[toSubKey(fieldKey)] = fieldValue
|
|
55
|
+
}
|
|
56
|
+
return acc
|
|
57
|
+
}, {})
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<SubForm
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
62
|
+
fields={subFields as S}
|
|
63
|
+
onFieldBlur={onFieldBlur}
|
|
64
|
+
onFieldFocus={onFieldFocus}
|
|
65
|
+
onFieldSubmit={onFieldSubmit}
|
|
66
|
+
onFieldValueChange={onFieldValueChange}
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
}
|
package/mantine/hooks.tsx
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
} from 'types/field'
|
|
36
36
|
import { type ListFieldsOfFields } from 'types/list_fields_of_fields'
|
|
37
37
|
import { type StringFieldsOfFields } from 'types/string_fields_of_fields'
|
|
38
|
+
import { type SubFormFields } from 'types/sub_form_fields'
|
|
38
39
|
import { type ValueTypeOfField } from 'types/value_type_of_field'
|
|
39
40
|
import {
|
|
40
41
|
createCheckbox,
|
|
@@ -57,6 +58,7 @@ import {
|
|
|
57
58
|
createRadioGroup,
|
|
58
59
|
type SuppliedRadioGroupProps,
|
|
59
60
|
} from './create_radio_group'
|
|
61
|
+
import { createSubForm } from './create_sub_form'
|
|
60
62
|
import {
|
|
61
63
|
createTextInput,
|
|
62
64
|
type SuppliedTextInputProps,
|
|
@@ -175,10 +177,18 @@ class MantineFormImpl<
|
|
|
175
177
|
> = new Cache(
|
|
176
178
|
createList.bind(this),
|
|
177
179
|
)
|
|
180
|
+
private readonly subFormCache: Cache<
|
|
181
|
+
// the cache cannot reference keys, so we just use any
|
|
182
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
183
|
+
[keyof AllFieldsOfFields<F>, ComponentType<any>, FormProps<F>],
|
|
184
|
+
ComponentType
|
|
185
|
+
> = new Cache(
|
|
186
|
+
createSubForm.bind(this),
|
|
187
|
+
)
|
|
178
188
|
|
|
179
189
|
@observable.ref
|
|
180
190
|
accessor fields: F
|
|
181
|
-
onFieldValueChange
|
|
191
|
+
onFieldValueChange!: <K extends keyof F>(this: void, key: K, value: F[K]['value']) => void
|
|
182
192
|
onFieldFocus: ((this: void, key: keyof F) => void) | undefined
|
|
183
193
|
onFieldBlur: ((this: void, key: keyof F) => void) | undefined
|
|
184
194
|
onFieldSubmit: ((this: void, key: keyof F) => boolean | void) | undefined
|
|
@@ -352,17 +362,31 @@ class MantineFormImpl<
|
|
|
352
362
|
list<
|
|
353
363
|
K extends keyof ListFieldsOfFields<F>,
|
|
354
364
|
>(valuePath: K): MantineFieldComponent<
|
|
355
|
-
SuppliedListProps
|
|
356
|
-
ComponentProps<typeof DefaultList<ElementOfArray<F[K]
|
|
365
|
+
SuppliedListProps<`${K}.${number}`>,
|
|
366
|
+
ComponentProps<typeof DefaultList<ElementOfArray<F[K]['value']>, K>>
|
|
357
367
|
> {
|
|
358
368
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
359
369
|
return this.listCache.retrieveOrCreate(
|
|
360
370
|
valuePath,
|
|
361
371
|
DefaultList,
|
|
362
372
|
) as MantineFieldComponent<
|
|
363
|
-
SuppliedListProps
|
|
364
|
-
ComponentProps<typeof DefaultList<ElementOfArray<F[K]
|
|
373
|
+
SuppliedListProps<`${K}.${number}`>,
|
|
374
|
+
ComponentProps<typeof DefaultList<ElementOfArray<F[K]['value']>, K>>,
|
|
365
375
|
ErrorOfField<F[K]>
|
|
366
376
|
>
|
|
367
377
|
}
|
|
378
|
+
|
|
379
|
+
// TODO have the returned component take any non-overlapping props as props
|
|
380
|
+
subForm<
|
|
381
|
+
K extends keyof AllFieldsOfFields<F>,
|
|
382
|
+
S extends SubFormFields<F, K>,
|
|
383
|
+
>(valuePath: K, SubForm: ComponentType<FormProps<S>>): ComponentType {
|
|
384
|
+
return this.subFormCache.retrieveOrCreate(
|
|
385
|
+
valuePath,
|
|
386
|
+
// strip props from component since we lose information in the cache
|
|
387
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
388
|
+
SubForm as ComponentType,
|
|
389
|
+
this,
|
|
390
|
+
)
|
|
391
|
+
}
|
|
368
392
|
}
|
|
@@ -38,29 +38,77 @@ exports[`mantine list hooks > renders Populated 1`] = `
|
|
|
38
38
|
class="m_b183c0a2 mantine-Code-root"
|
|
39
39
|
dir="ltr"
|
|
40
40
|
>
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
<span>
|
|
42
|
+
ValuePath:
|
|
43
|
+
$.0
|
|
44
|
+
</span>
|
|
45
|
+
<br />
|
|
46
|
+
<span>
|
|
47
|
+
Value:
|
|
48
|
+
A
|
|
49
|
+
</span>
|
|
50
|
+
<br />
|
|
51
|
+
<span>
|
|
52
|
+
Index:
|
|
53
|
+
0
|
|
54
|
+
</span>
|
|
43
55
|
</code>
|
|
44
56
|
<code
|
|
45
57
|
class="m_b183c0a2 mantine-Code-root"
|
|
46
58
|
dir="ltr"
|
|
47
59
|
>
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
<span>
|
|
61
|
+
ValuePath:
|
|
62
|
+
$.1
|
|
63
|
+
</span>
|
|
64
|
+
<br />
|
|
65
|
+
<span>
|
|
66
|
+
Value:
|
|
67
|
+
B
|
|
68
|
+
</span>
|
|
69
|
+
<br />
|
|
70
|
+
<span>
|
|
71
|
+
Index:
|
|
72
|
+
1
|
|
73
|
+
</span>
|
|
50
74
|
</code>
|
|
51
75
|
<code
|
|
52
76
|
class="m_b183c0a2 mantine-Code-root"
|
|
53
77
|
dir="ltr"
|
|
54
78
|
>
|
|
55
|
-
|
|
56
|
-
|
|
79
|
+
<span>
|
|
80
|
+
ValuePath:
|
|
81
|
+
$.2
|
|
82
|
+
</span>
|
|
83
|
+
<br />
|
|
84
|
+
<span>
|
|
85
|
+
Value:
|
|
86
|
+
C
|
|
87
|
+
</span>
|
|
88
|
+
<br />
|
|
89
|
+
<span>
|
|
90
|
+
Index:
|
|
91
|
+
2
|
|
92
|
+
</span>
|
|
57
93
|
</code>
|
|
58
94
|
<code
|
|
59
95
|
class="m_b183c0a2 mantine-Code-root"
|
|
60
96
|
dir="ltr"
|
|
61
97
|
>
|
|
62
|
-
|
|
63
|
-
|
|
98
|
+
<span>
|
|
99
|
+
ValuePath:
|
|
100
|
+
$.3
|
|
101
|
+
</span>
|
|
102
|
+
<br />
|
|
103
|
+
<span>
|
|
104
|
+
Value:
|
|
105
|
+
D
|
|
106
|
+
</span>
|
|
107
|
+
<br />
|
|
108
|
+
<span>
|
|
109
|
+
Index:
|
|
110
|
+
3
|
|
111
|
+
</span>
|
|
64
112
|
</code>
|
|
65
113
|
</div>
|
|
66
114
|
</div>
|
|
@@ -26,10 +26,20 @@ function Component(props: FormProps<{
|
|
|
26
26
|
>
|
|
27
27
|
<Stack>
|
|
28
28
|
<List>
|
|
29
|
-
{function (valuePath: ListPath) {
|
|
29
|
+
{function (valuePath: ListPath, value: string, index: number) {
|
|
30
30
|
return (
|
|
31
31
|
<Code key={valuePath}>
|
|
32
|
-
|
|
32
|
+
<span>
|
|
33
|
+
ValuePath: {valuePath}
|
|
34
|
+
</span>
|
|
35
|
+
<br />
|
|
36
|
+
<span>
|
|
37
|
+
Value: {value}
|
|
38
|
+
</span>
|
|
39
|
+
<br />
|
|
40
|
+
<span>
|
|
41
|
+
Index: {index}
|
|
42
|
+
</span>
|
|
33
43
|
</Code>
|
|
34
44
|
)
|
|
35
45
|
}}
|
|
@@ -72,10 +82,10 @@ export const Populated: Story = {
|
|
|
72
82
|
readonly: false,
|
|
73
83
|
required: false,
|
|
74
84
|
value: [
|
|
75
|
-
'
|
|
76
|
-
'
|
|
77
|
-
'
|
|
78
|
-
'
|
|
85
|
+
'A',
|
|
86
|
+
'B',
|
|
87
|
+
'C',
|
|
88
|
+
'D',
|
|
79
89
|
],
|
|
80
90
|
},
|
|
81
91
|
},
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Stack,
|
|
3
|
+
} from '@mantine/core'
|
|
4
|
+
import { action } from '@storybook/addon-actions'
|
|
5
|
+
import {
|
|
6
|
+
type Meta,
|
|
7
|
+
type StoryObj,
|
|
8
|
+
} from '@storybook/react'
|
|
9
|
+
import { type FormProps } from 'core/props'
|
|
10
|
+
import { useMantineForm } from 'mantine/hooks'
|
|
11
|
+
import { type Field } from 'types/field'
|
|
12
|
+
|
|
13
|
+
function SubFormImpl(props: FormProps<{
|
|
14
|
+
$: Field<string, string>,
|
|
15
|
+
}>) {
|
|
16
|
+
const form = useMantineForm(props)
|
|
17
|
+
const TextInput = form.textInput('$')
|
|
18
|
+
return <TextInput label='sub form' />
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function Component(props: FormProps<{
|
|
22
|
+
$: Field<string, string>,
|
|
23
|
+
'$.a': Field<string, string>,
|
|
24
|
+
}>) {
|
|
25
|
+
const form = useMantineForm(props)
|
|
26
|
+
const SubForm = form.subForm('$.a', SubFormImpl)
|
|
27
|
+
const TextInput = form.textInput('$')
|
|
28
|
+
return (
|
|
29
|
+
<Stack>
|
|
30
|
+
<TextInput label='form' />
|
|
31
|
+
<SubForm />
|
|
32
|
+
</Stack>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const meta: Meta<typeof Component> = {
|
|
37
|
+
component: Component,
|
|
38
|
+
args: {
|
|
39
|
+
onFieldBlur: action('onFieldBlur'),
|
|
40
|
+
onFieldFocus: action('onFieldFocus'),
|
|
41
|
+
onFieldSubmit: action('onFieldSubmit'),
|
|
42
|
+
onFieldValueChange: action('onFieldValueChange'),
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default meta
|
|
47
|
+
|
|
48
|
+
type Story = StoryObj<typeof Component>
|
|
49
|
+
|
|
50
|
+
export const Empty: Story = {
|
|
51
|
+
args: {
|
|
52
|
+
fields: {
|
|
53
|
+
$: {
|
|
54
|
+
readonly: false,
|
|
55
|
+
required: false,
|
|
56
|
+
value: '',
|
|
57
|
+
},
|
|
58
|
+
'$.a': {
|
|
59
|
+
readonly: false,
|
|
60
|
+
required: false,
|
|
61
|
+
value: '',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const Populated: Story = {
|
|
68
|
+
args: {
|
|
69
|
+
fields: {
|
|
70
|
+
$: {
|
|
71
|
+
readonly: false,
|
|
72
|
+
required: false,
|
|
73
|
+
value: 'Hello',
|
|
74
|
+
},
|
|
75
|
+
'$.a': {
|
|
76
|
+
readonly: false,
|
|
77
|
+
required: false,
|
|
78
|
+
value: 'World',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const Required: Story = {
|
|
85
|
+
args: {
|
|
86
|
+
fields: {
|
|
87
|
+
$: {
|
|
88
|
+
readonly: false,
|
|
89
|
+
required: true,
|
|
90
|
+
value: 'xxx',
|
|
91
|
+
},
|
|
92
|
+
'$.a': {
|
|
93
|
+
readonly: false,
|
|
94
|
+
required: true,
|
|
95
|
+
value: 'yyy',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const Disabled: Story = {
|
|
102
|
+
args: {
|
|
103
|
+
fields: {
|
|
104
|
+
$: {
|
|
105
|
+
readonly: true,
|
|
106
|
+
required: false,
|
|
107
|
+
value: 'xxx',
|
|
108
|
+
},
|
|
109
|
+
'$.a': {
|
|
110
|
+
readonly: true,
|
|
111
|
+
required: false,
|
|
112
|
+
value: 'yyy',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const CustomError: Story = {
|
|
119
|
+
args: {
|
|
120
|
+
fields: {
|
|
121
|
+
$: {
|
|
122
|
+
readonly: false,
|
|
123
|
+
required: false,
|
|
124
|
+
value: 'xxx',
|
|
125
|
+
error: 'form error',
|
|
126
|
+
},
|
|
127
|
+
'$.a': {
|
|
128
|
+
readonly: false,
|
|
129
|
+
required: false,
|
|
130
|
+
value: 'xxx',
|
|
131
|
+
error: 'sub form error',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Field } from 'types/field'
|
|
2
|
+
import { type ListFieldsOfFields } from 'types/list_fields_of_fields'
|
|
3
|
+
|
|
4
|
+
describe('ListFieldsOfFields', () => {
|
|
5
|
+
it('matches the expected type of an empty set of fields', () => {
|
|
6
|
+
type T = ListFieldsOfFields<{}>
|
|
7
|
+
expectTypeOf<T>().toEqualTypeOf<{}>()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('matches the expected type of a set of fields containing a single list', () => {
|
|
11
|
+
type X = {
|
|
12
|
+
l: Field<number[]>,
|
|
13
|
+
}
|
|
14
|
+
type T = ListFieldsOfFields<X>
|
|
15
|
+
expectTypeOf<T>().toEqualTypeOf<X>()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('matches the expected type of a set of fields containing a multiple fields, including a list', () => {
|
|
19
|
+
type X = {
|
|
20
|
+
readonly a: Field<number>,
|
|
21
|
+
readonly b: Field<string>,
|
|
22
|
+
readonly l: Field<readonly number[]>,
|
|
23
|
+
}
|
|
24
|
+
type T = ListFieldsOfFields<X>
|
|
25
|
+
expectTypeOf<T>().toEqualTypeOf<{
|
|
26
|
+
readonly l: Field<readonly number[]>,
|
|
27
|
+
}>()
|
|
28
|
+
})
|
|
29
|
+
})
|