@strictly/react-form 0.0.3 → 0.0.4

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 (37) hide show
  1. package/.out/.storybook/main.js +3 -1
  2. package/.out/core/mobx/specs/sub_form_field_adapters.tests.d.ts +1 -0
  3. package/.out/core/mobx/specs/sub_form_field_adapters.tests.js +41 -0
  4. package/.out/core/mobx/sub_form_field_adapters.d.ts +7 -0
  5. package/.out/core/mobx/sub_form_field_adapters.js +8 -0
  6. package/.out/index.d.ts +1 -0
  7. package/.out/index.js +1 -0
  8. package/.out/mantine/create_sub_form.d.ts +6 -0
  9. package/.out/mantine/create_sub_form.js +40 -0
  10. package/.out/mantine/hooks.d.ts +5 -3
  11. package/.out/mantine/hooks.js +9 -0
  12. package/.out/mantine/specs/sub_form_hooks.stories.d.ts +15 -0
  13. package/.out/mantine/specs/sub_form_hooks.stories.js +107 -0
  14. package/.out/tsconfig.tsbuildinfo +1 -1
  15. package/.out/types/specs/list_fields_of_fields.tests.d.ts +1 -0
  16. package/.out/types/specs/list_fields_of_fields.tests.js +12 -0
  17. package/.out/types/specs/sub_form_fields.tests.d.ts +1 -0
  18. package/.out/types/specs/sub_form_fields.tests.js +12 -0
  19. package/.out/types/sub_form_fields.d.ts +7 -0
  20. package/.out/types/sub_form_fields.js +1 -0
  21. package/.storybook/main.ts +3 -1
  22. package/.turbo/turbo-build.log +8 -8
  23. package/.turbo/turbo-check-types.log +1 -1
  24. package/core/mobx/specs/sub_form_field_adapters.tests.ts +59 -0
  25. package/core/mobx/sub_form_field_adapters.ts +21 -0
  26. package/dist/index.cjs +78 -6
  27. package/dist/index.d.cts +17 -4
  28. package/dist/index.d.ts +17 -4
  29. package/dist/index.js +77 -6
  30. package/index.ts +1 -0
  31. package/mantine/create_sub_form.tsx +70 -0
  32. package/mantine/hooks.tsx +29 -6
  33. package/mantine/specs/sub_form_hooks.stories.tsx +135 -0
  34. package/package.json +1 -1
  35. package/types/specs/list_fields_of_fields.tests.ts +29 -0
  36. package/types/specs/sub_form_fields.tests.ts +22 -0
  37. package/types/sub_form_fields.ts +7 -0
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ describe('ListFieldsOfFields', () => {
2
+ it('matches the expected type of an empty set of fields', () => {
3
+ expectTypeOf().toEqualTypeOf();
4
+ });
5
+ it('matches the expected type of a set of fields containing a single list', () => {
6
+ expectTypeOf().toEqualTypeOf();
7
+ });
8
+ it('matches the expected type of a set of fields containing a multiple fields, including a list', () => {
9
+ expectTypeOf().toEqualTypeOf();
10
+ });
11
+ });
12
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ describe('SubFormFields', () => {
2
+ it('works on single field', () => {
3
+ expectTypeOf().toEqualTypeOf();
4
+ });
5
+ it('works on more two fields', () => {
6
+ expectTypeOf().toEqualTypeOf();
7
+ });
8
+ it('works on subfields', () => {
9
+ expectTypeOf().toEqualTypeOf();
10
+ });
11
+ });
12
+ export {};
@@ -0,0 +1,7 @@
1
+ import { type StringConcatOf } from '@strictly/base';
2
+ import { type Fields } from './field';
3
+ export type SubFormFields<F extends Fields, P extends keyof F> = P extends string ? {
4
+ [K in keyof F as K extends StringConcatOf<`${P}.`, infer S> ? `$.${S}` : never]: F[K];
5
+ } & {
6
+ $: F[P];
7
+ } : never;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,10 +1,12 @@
1
1
  /* eslint-env node */
2
2
  import { type StorybookConfig } from '@storybook/react-vite'
3
+ import { createRequire } from 'module'
3
4
  import {
4
5
  dirname,
5
6
  join,
6
- } from 'path'
7
+ } from 'node:path'
7
8
 
9
+ const require = createRequire(import.meta.url)
8
10
  /**
9
11
  * This function is used to resolve the absolute path of a package.
10
12
  * It is needed in projects that use Yarn PnP or are set up within a monorepo.
@@ -7,12 +7,12 @@ $ tsup
7
7
  CLI Target: esnext
8
8
  CJS Build start
9
9
  ESM Build start
10
- CJS dist/index.cjs 44.90 KB
11
- CJS ⚡️ Build success in 113ms
12
- ESM dist/index.js 41.31 KB
13
- ESM ⚡️ Build success in 115ms
10
+ CJS dist/index.cjs 46.90 KB
11
+ CJS ⚡️ Build success in 104ms
12
+ ESM dist/index.js 43.20 KB
13
+ ESM ⚡️ Build success in 106ms
14
14
  DTS Build start
15
- DTS ⚡️ Build success in 9820ms
16
- DTS dist/index.d.cts 34.05 KB
17
- DTS dist/index.d.ts 34.05 KB
18
- Done in 10.94s.
15
+ DTS ⚡️ Build success in 9810ms
16
+ DTS dist/index.d.cts 34.83 KB
17
+ DTS dist/index.d.ts 34.83 KB
18
+ Done in 10.96s.
@@ -1,3 +1,3 @@
1
1
  yarn run v1.22.22
2
2
  $ tsc
3
- Done in 7.59s.
3
+ Done in 7.63s.
@@ -0,0 +1,59 @@
1
+ import { type FieldAdapter } from 'core/mobx/field_adapter'
2
+ import {
3
+ subFormFieldAdapters,
4
+ } from 'core/mobx/sub_form_field_adapters'
5
+ import { mockDeep } from 'vitest-mock-extended'
6
+
7
+ describe('subFormFieldAdapters', () => {
8
+ const fieldAdapter1: FieldAdapter<string, boolean> = mockDeep()
9
+ const fieldAdapter2: FieldAdapter<number, boolean> = mockDeep()
10
+
11
+ describe('empty value', () => {
12
+ const adapters = subFormFieldAdapters({}, '$.a')
13
+
14
+ it('equals expected type', () => {
15
+ expectTypeOf(adapters).toEqualTypeOf<{}>()
16
+ })
17
+
18
+ it('equals expected value', () => {
19
+ expect(adapters).toEqual({})
20
+ })
21
+ })
22
+
23
+ describe('single adapter', () => {
24
+ const adapters = subFormFieldAdapters({
25
+ $: fieldAdapter1,
26
+ }, '$.a')
27
+
28
+ it('equals expected type', () => {
29
+ expectTypeOf(adapters).toEqualTypeOf<{
30
+ '$.a': FieldAdapter<string, boolean>,
31
+ }>()
32
+ })
33
+
34
+ it('equals expected value', () => {
35
+ expect(adapters).toEqual({ '$.a': fieldAdapter1 })
36
+ })
37
+ })
38
+
39
+ describe('multiple adapters', () => {
40
+ const adapters = subFormFieldAdapters({
41
+ '$.x': fieldAdapter1,
42
+ '$.y': fieldAdapter2,
43
+ }, '$.a')
44
+
45
+ it('equals expected type', () => {
46
+ expectTypeOf(adapters).toEqualTypeOf<{
47
+ '$.a.x': FieldAdapter<string, boolean>,
48
+ '$.a.y': FieldAdapter<number, boolean>,
49
+ }>()
50
+ })
51
+
52
+ it('equals expected value', () => {
53
+ expect(adapters).toEqual({
54
+ '$.a.x': fieldAdapter1,
55
+ '$.a.y': fieldAdapter2,
56
+ })
57
+ })
58
+ })
59
+ })
@@ -0,0 +1,21 @@
1
+ import { type StringConcatOf } from '@strictly/base'
2
+ import { type FieldAdapter } from './field_adapter'
3
+
4
+ type SubFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string> = {
5
+ [K in keyof SubAdapters as K extends StringConcatOf<'$', infer S> ? `${P}${S}` : never]: SubAdapters[K]
6
+ }
7
+
8
+ export function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string>(
9
+ subAdapters: SubAdapters,
10
+ prefix: P,
11
+ ): SubFormFieldAdapters<SubAdapters, P> {
12
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
13
+ return Object.entries(subAdapters).reduce<Record<string, FieldAdapter>>((acc, [
14
+ subKey,
15
+ subValue,
16
+ ]) => {
17
+ const key = subKey.replace('$', prefix)
18
+ acc[key] = subValue
19
+ return acc
20
+ }, {}) as SubFormFieldAdapters<SubAdapters, P>
21
+ }
package/dist/index.cjs CHANGED
@@ -44,6 +44,7 @@ __export(index_exports, {
44
44
  mergeFieldAdaptersWithTwoWayConverter: () => mergeFieldAdaptersWithTwoWayConverter,
45
45
  mergeValidators: () => mergeValidators,
46
46
  prototypingFieldValueFactory: () => prototypingFieldValueFactory,
47
+ subFormFieldAdapters: () => subFormFieldAdapters,
47
48
  useMantineForm: () => useMantineForm,
48
49
  usePartialComponent: () => usePartialComponent,
49
50
  usePartialObserverComponent: () => usePartialObserverComponent,
@@ -774,6 +775,18 @@ function mergeAdaptersWithValidators(adapters, validators) {
774
775
  );
775
776
  }
776
777
 
778
+ // core/mobx/sub_form_field_adapters.ts
779
+ function subFormFieldAdapters(subAdapters, prefix) {
780
+ return Object.entries(subAdapters).reduce((acc, [
781
+ subKey,
782
+ subValue
783
+ ]) => {
784
+ const key = subKey.replace("$", prefix);
785
+ acc[key] = subValue;
786
+ return acc;
787
+ }, {});
788
+ }
789
+
777
790
  // field_converters/integer_to_string_converter.ts
778
791
  var IntegerToStringConverter = class {
779
792
  constructor(isNanError, base = 10) {
@@ -1282,8 +1295,53 @@ function createRadioGroup(valuePath, RadioGroup) {
1282
1295
  return createUnsafePartialObserverComponent(RadioGroup, propSource, ["ErrorRenderer"]);
1283
1296
  }
1284
1297
 
1285
- // mantine/create_text_input.tsx
1298
+ // mantine/create_sub_form.tsx
1299
+ var import_mobx_react2 = require("mobx-react");
1286
1300
  var import_jsx_runtime5 = require("react/jsx-runtime");
1301
+ function createSubForm(valuePath, SubForm, observableProps) {
1302
+ function toKey(subKey) {
1303
+ return subKey.replace("$", valuePath);
1304
+ }
1305
+ function toSubKey(key) {
1306
+ return key.replace(valuePath, "$");
1307
+ }
1308
+ function onFieldValueChange(subKey, value) {
1309
+ observableProps.onFieldValueChange(toKey(subKey), value);
1310
+ }
1311
+ function onFieldBlur(subKey) {
1312
+ observableProps.onFieldBlur?.(toKey(subKey));
1313
+ }
1314
+ function onFieldFocus(subKey) {
1315
+ observableProps.onFieldFocus?.(toKey(subKey));
1316
+ }
1317
+ function onFieldSubmit(subKey) {
1318
+ observableProps.onFieldSubmit?.(toKey(subKey));
1319
+ }
1320
+ return (0, import_mobx_react2.observer)(function() {
1321
+ const subFields = Object.entries(observableProps.fields).reduce((acc, [
1322
+ fieldKey,
1323
+ fieldValue
1324
+ ]) => {
1325
+ if (fieldKey.startsWith(valuePath)) {
1326
+ acc[toSubKey(fieldKey)] = fieldValue;
1327
+ }
1328
+ return acc;
1329
+ }, {});
1330
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1331
+ SubForm,
1332
+ {
1333
+ fields: subFields,
1334
+ onFieldBlur,
1335
+ onFieldFocus,
1336
+ onFieldSubmit,
1337
+ onFieldValueChange
1338
+ }
1339
+ );
1340
+ });
1341
+ }
1342
+
1343
+ // mantine/create_text_input.tsx
1344
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1287
1345
  function createTextInput(valuePath, TextInput) {
1288
1346
  const onChange = (e) => {
1289
1347
  this.onFieldValueChange?.(valuePath, e.target.value);
@@ -1317,7 +1375,7 @@ function createTextInput(valuePath, TextInput) {
1317
1375
  value,
1318
1376
  disabled: readonly,
1319
1377
  required,
1320
- error: error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ErrorRenderer, { error }),
1378
+ error: error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ErrorRenderer, { error }),
1321
1379
  onChange,
1322
1380
  onFocus,
1323
1381
  onBlur,
@@ -1332,7 +1390,7 @@ function createTextInput(valuePath, TextInput) {
1332
1390
  }
1333
1391
 
1334
1392
  // mantine/create_value_input.tsx
1335
- var import_jsx_runtime6 = require("react/jsx-runtime");
1393
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1336
1394
  function createValueInput(valuePath, ValueInput) {
1337
1395
  const onChange = (value) => {
1338
1396
  this.onFieldValueChange?.(valuePath, value);
@@ -1366,7 +1424,7 @@ function createValueInput(valuePath, ValueInput) {
1366
1424
  value,
1367
1425
  disabled: readonly,
1368
1426
  required,
1369
- error: error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ErrorRenderer, { error }),
1427
+ error: error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ErrorRenderer, { error }),
1370
1428
  onChange,
1371
1429
  onFocus,
1372
1430
  onBlur,
@@ -1381,9 +1439,9 @@ function createValueInput(valuePath, ValueInput) {
1381
1439
  }
1382
1440
 
1383
1441
  // mantine/hooks.tsx
1384
- var import_jsx_runtime7 = require("react/jsx-runtime");
1442
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1385
1443
  function SimpleSelect(props) {
1386
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_core.Select, { ...props });
1444
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_core.Select, { ...props });
1387
1445
  }
1388
1446
  function useMantineForm({
1389
1447
  onFieldValueChange,
@@ -1456,6 +1514,9 @@ var MantineFormImpl = class {
1456
1514
  listCache = new import_base6.Cache(
1457
1515
  createList.bind(this)
1458
1516
  );
1517
+ subFormCache = new import_base6.Cache(
1518
+ createSubForm.bind(this)
1519
+ );
1459
1520
  @import_mobx2.observable.ref
1460
1521
  accessor fields;
1461
1522
  onFieldValueChange;
@@ -1521,6 +1582,16 @@ var MantineFormImpl = class {
1521
1582
  DefaultList
1522
1583
  );
1523
1584
  }
1585
+ // TODO have the returned component take any non-overlapping props as props
1586
+ subForm(valuePath, SubForm) {
1587
+ return this.subFormCache.retrieveOrCreate(
1588
+ valuePath,
1589
+ // strip props from component since we lose information in the cache
1590
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1591
+ SubForm,
1592
+ this
1593
+ );
1594
+ }
1524
1595
  };
1525
1596
 
1526
1597
  // types/merge_validators.ts
@@ -1587,6 +1658,7 @@ function mergeValidators(validators1, validators2) {
1587
1658
  mergeFieldAdaptersWithTwoWayConverter,
1588
1659
  mergeValidators,
1589
1660
  prototypingFieldValueFactory,
1661
+ subFormFieldAdapters,
1590
1662
  useMantineForm,
1591
1663
  usePartialComponent,
1592
1664
  usePartialObserverComponent,
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Maybe, ElementOfArray, StringKeyOf as StringKeyOf$1, ExhaustiveArrayOfUnion, FriendlyExhaustiveArrayOfUnion } from '@strictly/base';
1
+ import { Maybe, ElementOfArray, StringConcatOf, StringKeyOf as StringKeyOf$1, ExhaustiveArrayOfUnion, FriendlyExhaustiveArrayOfUnion } from '@strictly/base';
2
2
  import { Type, ValueOfType, ReadonlyTypeOfType, FlattenedTypesOfType, ListTypeDef, FlattenedValuesOfType, MobxValueOfType, Accessor, Validator, UnionTypeDef, ValueTypesOfDiscriminatedUnion, LiteralTypeDef } from '@strictly/define';
3
3
  import { ValueOf, SimplifyDeep, ReadonlyDeep, StringKeyOf, Simplify } from 'type-fest';
4
4
  import { ComponentType, ComponentProps, ForwardRefExoticComponent, PropsWithoutRef, DependencyList } from 'react';
@@ -168,6 +168,11 @@ type MergedOfFieldAdaptersWithValidators<FieldAdapters extends Readonly<Record<K
168
168
  type MergedOfFieldAdapterWithValidator<A extends FieldAdapter, V extends Validator | undefined> = undefined extends V ? A : A extends FieldAdapter<infer From, infer To, infer E1, infer P1, infer C1> ? V extends Validator<From, infer E2, infer P2, infer C2> ? FieldAdapter<From, To, E1 | E2, P1 | P2, C1 | C2> : never : never;
169
169
  declare function mergeAdaptersWithValidators<FieldAdapters extends Readonly<Record<Key, FieldAdapter>>, Validators extends Readonly<Record<string, Validator>>, Key extends keyof Validators = keyof Validators>(adapters: FieldAdapters, validators: Validators): MergedOfFieldAdaptersWithValidators<FieldAdapters, Validators, Key>;
170
170
 
171
+ type SubFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string> = {
172
+ [K in keyof SubAdapters as K extends StringConcatOf<'$', infer S> ? `${P}${S}` : never]: SubAdapters[K];
173
+ };
174
+ declare function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string>(subAdapters: SubAdapters, prefix: P): SubFormFieldAdapters<SubAdapters, P>;
175
+
171
176
  /**
172
177
  * Used to extract the supported value paths from a presenter
173
178
  */
@@ -279,6 +284,12 @@ type StringFieldsOfFields<F extends Fields> = {
279
284
  [K in keyof F as ValueTypeOfField<F[K]> extends string | undefined | null ? K : never]: F[K];
280
285
  };
281
286
 
287
+ type SubFormFields<F extends Fields, P extends keyof F> = P extends string ? {
288
+ [K in keyof F as K extends StringConcatOf<`${P}.`, infer S> ? `$.${S}` : never]: F[K];
289
+ } & {
290
+ $: F[P];
291
+ } : never;
292
+
282
293
  type PartialComponent<Component extends ComponentType<any>, CurriedProps, AdditionalProps = {}> = Exclude<keyof CurriedProps, keyof ComponentProps<Component>> extends never ? UnsafePartialComponent<Component, CurriedProps, AdditionalProps> : keyof CurriedProps extends (string | number) ? `unmatched prop: ${Exclude<keyof CurriedProps, keyof ComponentProps<Component>>}` : Exclude<keyof CurriedProps, keyof ComponentProps<Component>>;
283
294
  type UnsafePartialComponent<Component extends ComponentType<any>, CurriedProps, AdditionalProps = {}> = ForwardRefExoticComponent<PropsWithoutRef<RemainingComponentProps<Component, CurriedProps> & AdditionalProps>>;
284
295
  declare function createSimplePartialComponent<Component extends ComponentType<any>, CurriedProps extends Partial<ComponentProps<Component>>>(Component: Component, curriedProps: CurriedProps): PartialComponent<Component, CurriedProps>;
@@ -357,8 +368,9 @@ declare class MantineFormImpl<F extends Fields> implements MantineForm<F> {
357
368
  private readonly radioCache;
358
369
  private readonly pillCache;
359
370
  private readonly listCache;
371
+ private readonly subFormCache;
360
372
  accessor fields: F;
361
- onFieldValueChange: (<K extends keyof F>(this: void, key: K, value: F[K]['value']) => void) | undefined;
373
+ onFieldValueChange: <K extends keyof F>(this: void, key: K, value: F[K]['value']) => void;
362
374
  onFieldFocus: ((this: void, key: keyof F) => void) | undefined;
363
375
  onFieldBlur: ((this: void, key: keyof F) => void) | undefined;
364
376
  onFieldSubmit: ((this: void, key: keyof F) => boolean | void) | undefined;
@@ -375,7 +387,8 @@ declare class MantineFormImpl<F extends Fields> implements MantineForm<F> {
375
387
  radio<K extends keyof StringFieldsOfFields<F>, P extends SuppliedRadioProps>(valuePath: K, value: ValueTypeOfField<F[K]>, Radio: ComponentType<P>): MantineFieldComponent<SuppliedRadioProps, P, ErrorOfField<F[K]>>;
376
388
  pill<K extends keyof AllFieldsOfFields<F>>(valuePath: K): MantineFieldComponent<SuppliedPillProps, PillProps, ErrorOfField<F[K]>>;
377
389
  pill<K extends keyof AllFieldsOfFields<F>, P extends SuppliedPillProps>(valuePath: K, Pill: ComponentType<P>): MantineFieldComponent<SuppliedPillProps, P>;
378
- list<K extends keyof ListFieldsOfFields<F>>(valuePath: K): MantineFieldComponent<SuppliedListProps<ElementOfArray<F[K]>>, ComponentProps<typeof DefaultList<ElementOfArray<F[K]>>>>;
390
+ list<K extends keyof ListFieldsOfFields<F>>(valuePath: K): MantineFieldComponent<SuppliedListProps<`${K}.${number}`>, ComponentProps<typeof DefaultList<`${K}.${number}`>>>;
391
+ subForm<K extends keyof AllFieldsOfFields<F>, S extends SubFormFields<F, K>>(valuePath: K, SubForm: ComponentType<FormProps<S>>): ComponentType;
379
392
  }
380
393
 
381
394
  type MergedOfValidators<Validators1 extends Partial<Readonly<Record<Keys, Validator>>>, Validators2 extends Partial<Readonly<Record<Keys, Validator>>>, Keys extends string = Extract<keyof Validators1 | keyof Validators2, string>> = Simplify<{
@@ -384,4 +397,4 @@ type MergedOfValidators<Validators1 extends Partial<Readonly<Record<Keys, Valida
384
397
  type MergedOfValidator<Validator1 extends Validator, Validator2 extends Validator> = Validator1 extends Validator<infer V, infer E1, infer P, infer C> ? Validator2 extends Validator<V, infer E2, P, C> ? Validator<V, E1 | E2, P, C> : never : never;
385
398
  declare function mergeValidators<Validators1 extends Partial<Readonly<Record<Keys, Validator>>>, Validators2 extends Partial<Readonly<Record<Keys, Validator>>>, Keys extends string = Extract<keyof Validators1 | keyof Validators2, string>>(validators1: Validators1, validators2: Validators2): MergedOfValidators<Validators1, Validators2, Keys>;
386
399
 
387
- export { AbstractSelectValueTypeConverter, type AnnotatedFieldConversion, type AnnotatedFieldConverter, type Annotation, DefaultErrorRenderer, type EditorProps, type ErrorOfField, type ErrorOfFieldAdapter, type ErrorRenderer, type ErrorRendererProps, type Field, type FieldAdapter, type FieldAdaptersOfValues, type FieldValueFactory, type Fields, type FlattenedAdaptersOfFields, type FlattenedConvertedFieldsOf, type FlattenedTypePathsToAdaptersOf, type FormFieldsOfFieldAdapters, type FormFieldsOfPresenter, FormModel, FormPresenter, type FormProps, type FromOfFieldAdapter, IntegerToStringConverter, type MergedOfFieldAdaptersWithTwoWayConverter, type MergedOfFieldAdaptersWithValidators, type MergedOfValidator, type MergedOfValidators, NullableToBooleanConverter, type PartialComponent, SelectDiscriminatedUnionConverter, SelectLiteralConverter, SelectStringConverter, type ToOfFieldAdapter, type ToValueOfPresenterValuePath, TrimmingStringConverter, type TwoWayFieldConverter, type TwoWayFieldConverterWithValueFactory, type UnreliableFieldConversion, UnreliableFieldConversionType, type UnreliableFieldConverter, type UnsafePartialComponent, type ValuePathOfFieldAdapter, type ValuePathsOfPresenter, type ValuePathsToAdaptersOf, adapter, adapterFromPrototype, adapterFromTwoWayConverter, createPartialComponent, createPartialObserverComponent, createSimplePartialComponent, createUnsafePartialObserverComponent, identityAdapter, listAdapter, mergeAdaptersWithValidators, mergeFieldAdaptersWithTwoWayConverter, mergeValidators, prototypingFieldValueFactory, useMantineForm, usePartialComponent, usePartialObserverComponent, validatingConverter };
400
+ export { AbstractSelectValueTypeConverter, type AnnotatedFieldConversion, type AnnotatedFieldConverter, type Annotation, DefaultErrorRenderer, type EditorProps, type ErrorOfField, type ErrorOfFieldAdapter, type ErrorRenderer, type ErrorRendererProps, type Field, type FieldAdapter, type FieldAdaptersOfValues, type FieldValueFactory, type Fields, type FlattenedAdaptersOfFields, type FlattenedConvertedFieldsOf, type FlattenedTypePathsToAdaptersOf, type FormFieldsOfFieldAdapters, type FormFieldsOfPresenter, FormModel, FormPresenter, type FormProps, type FromOfFieldAdapter, IntegerToStringConverter, type MergedOfFieldAdaptersWithTwoWayConverter, type MergedOfFieldAdaptersWithValidators, type MergedOfValidator, type MergedOfValidators, NullableToBooleanConverter, type PartialComponent, SelectDiscriminatedUnionConverter, SelectLiteralConverter, SelectStringConverter, type ToOfFieldAdapter, type ToValueOfPresenterValuePath, TrimmingStringConverter, type TwoWayFieldConverter, type TwoWayFieldConverterWithValueFactory, type UnreliableFieldConversion, UnreliableFieldConversionType, type UnreliableFieldConverter, type UnsafePartialComponent, type ValuePathOfFieldAdapter, type ValuePathsOfPresenter, type ValuePathsToAdaptersOf, adapter, adapterFromPrototype, adapterFromTwoWayConverter, createPartialComponent, createPartialObserverComponent, createSimplePartialComponent, createUnsafePartialObserverComponent, identityAdapter, listAdapter, mergeAdaptersWithValidators, mergeFieldAdaptersWithTwoWayConverter, mergeValidators, prototypingFieldValueFactory, subFormFieldAdapters, useMantineForm, usePartialComponent, usePartialObserverComponent, validatingConverter };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Maybe, ElementOfArray, StringKeyOf as StringKeyOf$1, ExhaustiveArrayOfUnion, FriendlyExhaustiveArrayOfUnion } from '@strictly/base';
1
+ import { Maybe, ElementOfArray, StringConcatOf, StringKeyOf as StringKeyOf$1, ExhaustiveArrayOfUnion, FriendlyExhaustiveArrayOfUnion } from '@strictly/base';
2
2
  import { Type, ValueOfType, ReadonlyTypeOfType, FlattenedTypesOfType, ListTypeDef, FlattenedValuesOfType, MobxValueOfType, Accessor, Validator, UnionTypeDef, ValueTypesOfDiscriminatedUnion, LiteralTypeDef } from '@strictly/define';
3
3
  import { ValueOf, SimplifyDeep, ReadonlyDeep, StringKeyOf, Simplify } from 'type-fest';
4
4
  import { ComponentType, ComponentProps, ForwardRefExoticComponent, PropsWithoutRef, DependencyList } from 'react';
@@ -168,6 +168,11 @@ type MergedOfFieldAdaptersWithValidators<FieldAdapters extends Readonly<Record<K
168
168
  type MergedOfFieldAdapterWithValidator<A extends FieldAdapter, V extends Validator | undefined> = undefined extends V ? A : A extends FieldAdapter<infer From, infer To, infer E1, infer P1, infer C1> ? V extends Validator<From, infer E2, infer P2, infer C2> ? FieldAdapter<From, To, E1 | E2, P1 | P2, C1 | C2> : never : never;
169
169
  declare function mergeAdaptersWithValidators<FieldAdapters extends Readonly<Record<Key, FieldAdapter>>, Validators extends Readonly<Record<string, Validator>>, Key extends keyof Validators = keyof Validators>(adapters: FieldAdapters, validators: Validators): MergedOfFieldAdaptersWithValidators<FieldAdapters, Validators, Key>;
170
170
 
171
+ type SubFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string> = {
172
+ [K in keyof SubAdapters as K extends StringConcatOf<'$', infer S> ? `${P}${S}` : never]: SubAdapters[K];
173
+ };
174
+ declare function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string>(subAdapters: SubAdapters, prefix: P): SubFormFieldAdapters<SubAdapters, P>;
175
+
171
176
  /**
172
177
  * Used to extract the supported value paths from a presenter
173
178
  */
@@ -279,6 +284,12 @@ type StringFieldsOfFields<F extends Fields> = {
279
284
  [K in keyof F as ValueTypeOfField<F[K]> extends string | undefined | null ? K : never]: F[K];
280
285
  };
281
286
 
287
+ type SubFormFields<F extends Fields, P extends keyof F> = P extends string ? {
288
+ [K in keyof F as K extends StringConcatOf<`${P}.`, infer S> ? `$.${S}` : never]: F[K];
289
+ } & {
290
+ $: F[P];
291
+ } : never;
292
+
282
293
  type PartialComponent<Component extends ComponentType<any>, CurriedProps, AdditionalProps = {}> = Exclude<keyof CurriedProps, keyof ComponentProps<Component>> extends never ? UnsafePartialComponent<Component, CurriedProps, AdditionalProps> : keyof CurriedProps extends (string | number) ? `unmatched prop: ${Exclude<keyof CurriedProps, keyof ComponentProps<Component>>}` : Exclude<keyof CurriedProps, keyof ComponentProps<Component>>;
283
294
  type UnsafePartialComponent<Component extends ComponentType<any>, CurriedProps, AdditionalProps = {}> = ForwardRefExoticComponent<PropsWithoutRef<RemainingComponentProps<Component, CurriedProps> & AdditionalProps>>;
284
295
  declare function createSimplePartialComponent<Component extends ComponentType<any>, CurriedProps extends Partial<ComponentProps<Component>>>(Component: Component, curriedProps: CurriedProps): PartialComponent<Component, CurriedProps>;
@@ -357,8 +368,9 @@ declare class MantineFormImpl<F extends Fields> implements MantineForm<F> {
357
368
  private readonly radioCache;
358
369
  private readonly pillCache;
359
370
  private readonly listCache;
371
+ private readonly subFormCache;
360
372
  accessor fields: F;
361
- onFieldValueChange: (<K extends keyof F>(this: void, key: K, value: F[K]['value']) => void) | undefined;
373
+ onFieldValueChange: <K extends keyof F>(this: void, key: K, value: F[K]['value']) => void;
362
374
  onFieldFocus: ((this: void, key: keyof F) => void) | undefined;
363
375
  onFieldBlur: ((this: void, key: keyof F) => void) | undefined;
364
376
  onFieldSubmit: ((this: void, key: keyof F) => boolean | void) | undefined;
@@ -375,7 +387,8 @@ declare class MantineFormImpl<F extends Fields> implements MantineForm<F> {
375
387
  radio<K extends keyof StringFieldsOfFields<F>, P extends SuppliedRadioProps>(valuePath: K, value: ValueTypeOfField<F[K]>, Radio: ComponentType<P>): MantineFieldComponent<SuppliedRadioProps, P, ErrorOfField<F[K]>>;
376
388
  pill<K extends keyof AllFieldsOfFields<F>>(valuePath: K): MantineFieldComponent<SuppliedPillProps, PillProps, ErrorOfField<F[K]>>;
377
389
  pill<K extends keyof AllFieldsOfFields<F>, P extends SuppliedPillProps>(valuePath: K, Pill: ComponentType<P>): MantineFieldComponent<SuppliedPillProps, P>;
378
- list<K extends keyof ListFieldsOfFields<F>>(valuePath: K): MantineFieldComponent<SuppliedListProps<ElementOfArray<F[K]>>, ComponentProps<typeof DefaultList<ElementOfArray<F[K]>>>>;
390
+ list<K extends keyof ListFieldsOfFields<F>>(valuePath: K): MantineFieldComponent<SuppliedListProps<`${K}.${number}`>, ComponentProps<typeof DefaultList<`${K}.${number}`>>>;
391
+ subForm<K extends keyof AllFieldsOfFields<F>, S extends SubFormFields<F, K>>(valuePath: K, SubForm: ComponentType<FormProps<S>>): ComponentType;
379
392
  }
380
393
 
381
394
  type MergedOfValidators<Validators1 extends Partial<Readonly<Record<Keys, Validator>>>, Validators2 extends Partial<Readonly<Record<Keys, Validator>>>, Keys extends string = Extract<keyof Validators1 | keyof Validators2, string>> = Simplify<{
@@ -384,4 +397,4 @@ type MergedOfValidators<Validators1 extends Partial<Readonly<Record<Keys, Valida
384
397
  type MergedOfValidator<Validator1 extends Validator, Validator2 extends Validator> = Validator1 extends Validator<infer V, infer E1, infer P, infer C> ? Validator2 extends Validator<V, infer E2, P, C> ? Validator<V, E1 | E2, P, C> : never : never;
385
398
  declare function mergeValidators<Validators1 extends Partial<Readonly<Record<Keys, Validator>>>, Validators2 extends Partial<Readonly<Record<Keys, Validator>>>, Keys extends string = Extract<keyof Validators1 | keyof Validators2, string>>(validators1: Validators1, validators2: Validators2): MergedOfValidators<Validators1, Validators2, Keys>;
386
399
 
387
- export { AbstractSelectValueTypeConverter, type AnnotatedFieldConversion, type AnnotatedFieldConverter, type Annotation, DefaultErrorRenderer, type EditorProps, type ErrorOfField, type ErrorOfFieldAdapter, type ErrorRenderer, type ErrorRendererProps, type Field, type FieldAdapter, type FieldAdaptersOfValues, type FieldValueFactory, type Fields, type FlattenedAdaptersOfFields, type FlattenedConvertedFieldsOf, type FlattenedTypePathsToAdaptersOf, type FormFieldsOfFieldAdapters, type FormFieldsOfPresenter, FormModel, FormPresenter, type FormProps, type FromOfFieldAdapter, IntegerToStringConverter, type MergedOfFieldAdaptersWithTwoWayConverter, type MergedOfFieldAdaptersWithValidators, type MergedOfValidator, type MergedOfValidators, NullableToBooleanConverter, type PartialComponent, SelectDiscriminatedUnionConverter, SelectLiteralConverter, SelectStringConverter, type ToOfFieldAdapter, type ToValueOfPresenterValuePath, TrimmingStringConverter, type TwoWayFieldConverter, type TwoWayFieldConverterWithValueFactory, type UnreliableFieldConversion, UnreliableFieldConversionType, type UnreliableFieldConverter, type UnsafePartialComponent, type ValuePathOfFieldAdapter, type ValuePathsOfPresenter, type ValuePathsToAdaptersOf, adapter, adapterFromPrototype, adapterFromTwoWayConverter, createPartialComponent, createPartialObserverComponent, createSimplePartialComponent, createUnsafePartialObserverComponent, identityAdapter, listAdapter, mergeAdaptersWithValidators, mergeFieldAdaptersWithTwoWayConverter, mergeValidators, prototypingFieldValueFactory, useMantineForm, usePartialComponent, usePartialObserverComponent, validatingConverter };
400
+ export { AbstractSelectValueTypeConverter, type AnnotatedFieldConversion, type AnnotatedFieldConverter, type Annotation, DefaultErrorRenderer, type EditorProps, type ErrorOfField, type ErrorOfFieldAdapter, type ErrorRenderer, type ErrorRendererProps, type Field, type FieldAdapter, type FieldAdaptersOfValues, type FieldValueFactory, type Fields, type FlattenedAdaptersOfFields, type FlattenedConvertedFieldsOf, type FlattenedTypePathsToAdaptersOf, type FormFieldsOfFieldAdapters, type FormFieldsOfPresenter, FormModel, FormPresenter, type FormProps, type FromOfFieldAdapter, IntegerToStringConverter, type MergedOfFieldAdaptersWithTwoWayConverter, type MergedOfFieldAdaptersWithValidators, type MergedOfValidator, type MergedOfValidators, NullableToBooleanConverter, type PartialComponent, SelectDiscriminatedUnionConverter, SelectLiteralConverter, SelectStringConverter, type ToOfFieldAdapter, type ToValueOfPresenterValuePath, TrimmingStringConverter, type TwoWayFieldConverter, type TwoWayFieldConverterWithValueFactory, type UnreliableFieldConversion, UnreliableFieldConversionType, type UnreliableFieldConverter, type UnsafePartialComponent, type ValuePathOfFieldAdapter, type ValuePathsOfPresenter, type ValuePathsToAdaptersOf, adapter, adapterFromPrototype, adapterFromTwoWayConverter, createPartialComponent, createPartialObserverComponent, createSimplePartialComponent, createUnsafePartialObserverComponent, identityAdapter, listAdapter, mergeAdaptersWithValidators, mergeFieldAdaptersWithTwoWayConverter, mergeValidators, prototypingFieldValueFactory, subFormFieldAdapters, useMantineForm, usePartialComponent, usePartialObserverComponent, validatingConverter };
package/dist/index.js CHANGED
@@ -746,6 +746,18 @@ function mergeAdaptersWithValidators(adapters, validators) {
746
746
  );
747
747
  }
748
748
 
749
+ // core/mobx/sub_form_field_adapters.ts
750
+ function subFormFieldAdapters(subAdapters, prefix) {
751
+ return Object.entries(subAdapters).reduce((acc, [
752
+ subKey,
753
+ subValue
754
+ ]) => {
755
+ const key = subKey.replace("$", prefix);
756
+ acc[key] = subValue;
757
+ return acc;
758
+ }, {});
759
+ }
760
+
749
761
  // field_converters/integer_to_string_converter.ts
750
762
  var IntegerToStringConverter = class {
751
763
  constructor(isNanError, base = 10) {
@@ -1279,8 +1291,53 @@ function createRadioGroup(valuePath, RadioGroup) {
1279
1291
  return createUnsafePartialObserverComponent(RadioGroup, propSource, ["ErrorRenderer"]);
1280
1292
  }
1281
1293
 
1282
- // mantine/create_text_input.tsx
1294
+ // mantine/create_sub_form.tsx
1295
+ import { observer as observer2 } from "mobx-react";
1283
1296
  import { jsx as jsx5 } from "react/jsx-runtime";
1297
+ function createSubForm(valuePath, SubForm, observableProps) {
1298
+ function toKey(subKey) {
1299
+ return subKey.replace("$", valuePath);
1300
+ }
1301
+ function toSubKey(key) {
1302
+ return key.replace(valuePath, "$");
1303
+ }
1304
+ function onFieldValueChange(subKey, value) {
1305
+ observableProps.onFieldValueChange(toKey(subKey), value);
1306
+ }
1307
+ function onFieldBlur(subKey) {
1308
+ observableProps.onFieldBlur?.(toKey(subKey));
1309
+ }
1310
+ function onFieldFocus(subKey) {
1311
+ observableProps.onFieldFocus?.(toKey(subKey));
1312
+ }
1313
+ function onFieldSubmit(subKey) {
1314
+ observableProps.onFieldSubmit?.(toKey(subKey));
1315
+ }
1316
+ return observer2(function() {
1317
+ const subFields = Object.entries(observableProps.fields).reduce((acc, [
1318
+ fieldKey,
1319
+ fieldValue
1320
+ ]) => {
1321
+ if (fieldKey.startsWith(valuePath)) {
1322
+ acc[toSubKey(fieldKey)] = fieldValue;
1323
+ }
1324
+ return acc;
1325
+ }, {});
1326
+ return /* @__PURE__ */ jsx5(
1327
+ SubForm,
1328
+ {
1329
+ fields: subFields,
1330
+ onFieldBlur,
1331
+ onFieldFocus,
1332
+ onFieldSubmit,
1333
+ onFieldValueChange
1334
+ }
1335
+ );
1336
+ });
1337
+ }
1338
+
1339
+ // mantine/create_text_input.tsx
1340
+ import { jsx as jsx6 } from "react/jsx-runtime";
1284
1341
  function createTextInput(valuePath, TextInput) {
1285
1342
  const onChange = (e) => {
1286
1343
  this.onFieldValueChange?.(valuePath, e.target.value);
@@ -1314,7 +1371,7 @@ function createTextInput(valuePath, TextInput) {
1314
1371
  value,
1315
1372
  disabled: readonly,
1316
1373
  required,
1317
- error: error && /* @__PURE__ */ jsx5(ErrorRenderer, { error }),
1374
+ error: error && /* @__PURE__ */ jsx6(ErrorRenderer, { error }),
1318
1375
  onChange,
1319
1376
  onFocus,
1320
1377
  onBlur,
@@ -1329,7 +1386,7 @@ function createTextInput(valuePath, TextInput) {
1329
1386
  }
1330
1387
 
1331
1388
  // mantine/create_value_input.tsx
1332
- import { jsx as jsx6 } from "react/jsx-runtime";
1389
+ import { jsx as jsx7 } from "react/jsx-runtime";
1333
1390
  function createValueInput(valuePath, ValueInput) {
1334
1391
  const onChange = (value) => {
1335
1392
  this.onFieldValueChange?.(valuePath, value);
@@ -1363,7 +1420,7 @@ function createValueInput(valuePath, ValueInput) {
1363
1420
  value,
1364
1421
  disabled: readonly,
1365
1422
  required,
1366
- error: error && /* @__PURE__ */ jsx6(ErrorRenderer, { error }),
1423
+ error: error && /* @__PURE__ */ jsx7(ErrorRenderer, { error }),
1367
1424
  onChange,
1368
1425
  onFocus,
1369
1426
  onBlur,
@@ -1378,9 +1435,9 @@ function createValueInput(valuePath, ValueInput) {
1378
1435
  }
1379
1436
 
1380
1437
  // mantine/hooks.tsx
1381
- import { jsx as jsx7 } from "react/jsx-runtime";
1438
+ import { jsx as jsx8 } from "react/jsx-runtime";
1382
1439
  function SimpleSelect(props) {
1383
- return /* @__PURE__ */ jsx7(Select, { ...props });
1440
+ return /* @__PURE__ */ jsx8(Select, { ...props });
1384
1441
  }
1385
1442
  function useMantineForm({
1386
1443
  onFieldValueChange,
@@ -1453,6 +1510,9 @@ var MantineFormImpl = class {
1453
1510
  listCache = new Cache(
1454
1511
  createList.bind(this)
1455
1512
  );
1513
+ subFormCache = new Cache(
1514
+ createSubForm.bind(this)
1515
+ );
1456
1516
  @observable2.ref
1457
1517
  accessor fields;
1458
1518
  onFieldValueChange;
@@ -1518,6 +1578,16 @@ var MantineFormImpl = class {
1518
1578
  DefaultList
1519
1579
  );
1520
1580
  }
1581
+ // TODO have the returned component take any non-overlapping props as props
1582
+ subForm(valuePath, SubForm) {
1583
+ return this.subFormCache.retrieveOrCreate(
1584
+ valuePath,
1585
+ // strip props from component since we lose information in the cache
1586
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1587
+ SubForm,
1588
+ this
1589
+ );
1590
+ }
1521
1591
  };
1522
1592
 
1523
1593
  // types/merge_validators.ts
@@ -1586,6 +1656,7 @@ export {
1586
1656
  mergeFieldAdaptersWithTwoWayConverter,
1587
1657
  mergeValidators,
1588
1658
  prototypingFieldValueFactory,
1659
+ subFormFieldAdapters,
1589
1660
  useMantineForm,
1590
1661
  usePartialComponent,
1591
1662
  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'