@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.
Files changed (50) hide show
  1. package/.out/.storybook/main.js +3 -1
  2. package/.out/core/mobx/field_adapter_builder.d.ts +1 -1
  3. package/.out/core/mobx/field_adapter_builder.js +1 -2
  4. package/.out/core/mobx/specs/sub_form_field_adapters.tests.d.ts +1 -0
  5. package/.out/core/mobx/specs/sub_form_field_adapters.tests.js +41 -0
  6. package/.out/core/mobx/sub_form_field_adapters.d.ts +7 -0
  7. package/.out/core/mobx/sub_form_field_adapters.js +8 -0
  8. package/.out/index.d.ts +1 -0
  9. package/.out/index.js +1 -0
  10. package/.out/mantine/create_list.d.ts +5 -4
  11. package/.out/mantine/create_list.js +4 -2
  12. package/.out/mantine/create_sub_form.d.ts +6 -0
  13. package/.out/mantine/create_sub_form.js +40 -0
  14. package/.out/mantine/hooks.d.ts +5 -2
  15. package/.out/mantine/hooks.js +9 -0
  16. package/.out/mantine/specs/list_hooks.stories.js +6 -6
  17. package/.out/mantine/specs/sub_form_hooks.stories.d.ts +15 -0
  18. package/.out/mantine/specs/sub_form_hooks.stories.js +107 -0
  19. package/.out/tsconfig.tsbuildinfo +1 -1
  20. package/.out/types/specs/list_fields_of_fields.tests.d.ts +1 -0
  21. package/.out/types/specs/list_fields_of_fields.tests.js +12 -0
  22. package/.out/types/specs/sub_form_fields.tests.d.ts +1 -0
  23. package/.out/types/specs/sub_form_fields.tests.js +12 -0
  24. package/.out/types/sub_form_fields.d.ts +7 -0
  25. package/.out/types/sub_form_fields.js +1 -0
  26. package/.storybook/main.ts +3 -1
  27. package/.turbo/turbo-build.log +8 -8
  28. package/.turbo/turbo-check-types.log +1 -1
  29. package/.turbo/turbo-release$colon$exports.log +1 -1
  30. package/core/mobx/field_adapter_builder.ts +3 -4
  31. package/core/mobx/specs/sub_form_field_adapters.tests.ts +59 -0
  32. package/core/mobx/sub_form_field_adapters.ts +21 -0
  33. package/dist/index.cjs +86 -24
  34. package/dist/index.d.cts +22 -8
  35. package/dist/index.d.ts +22 -8
  36. package/dist/index.js +85 -24
  37. package/index.ts +1 -0
  38. package/mantine/create_list.tsx +10 -4
  39. package/mantine/create_sub_form.tsx +70 -0
  40. package/mantine/hooks.tsx +29 -5
  41. package/mantine/specs/__snapshots__/list_hooks.tests.tsx.snap +56 -8
  42. package/mantine/specs/list_hooks.stories.tsx +16 -6
  43. package/mantine/specs/sub_form_hooks.stories.tsx +135 -0
  44. package/package.json +1 -1
  45. package/types/specs/list_fields_of_fields.tests.ts +29 -0
  46. package/types/specs/sub_form_fields.tests.ts +22 -0
  47. package/types/sub_form_fields.ts +7 -0
  48. package/.out/field_converters/list_converter.d.ts +0 -2
  49. package/.out/field_converters/list_converter.js +0 -13
  50. 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
- listConverter(),
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
- return children(value, index);
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/create_text_input.tsx
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__ */ jsx5(ErrorRenderer, { error }),
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 jsx6 } from "react/jsx-runtime";
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__ */ jsx6(ErrorRenderer, { error }),
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 jsx7 } from "react/jsx-runtime";
1428
+ import { jsx as jsx8 } from "react/jsx-runtime";
1382
1429
  function SimpleSelect(props) {
1383
- return /* @__PURE__ */ jsx7(Select, { ...props });
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'
@@ -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
- return children(value, index)
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: (<K extends keyof F>(this: void, key: K, value: F[K]['value']) => void) | undefined
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<ElementOfArray<F[K]>>,
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<ElementOfArray<F[K]>>,
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
- ValuePath:
42
- $.4
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
- ValuePath:
49
- $.6
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
- ValuePath:
56
- $.19
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
- ValuePath:
63
- $.0
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
- ValuePath: {valuePath}
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
- '$.4',
76
- '$.6',
77
- '$.19',
78
- '$.0',
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
@@ -69,7 +69,7 @@
69
69
  "test:watch": "vitest"
70
70
  },
71
71
  "type": "module",
72
- "version": "0.0.3",
72
+ "version": "0.0.5",
73
73
  "exports": {
74
74
  ".": {
75
75
  "import": {
@@ -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
+ })