@strictly/react-form 0.0.15 → 0.0.17

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 (40) hide show
  1. package/.out/core/mobx/field_adapter_builder.d.ts +1 -1
  2. package/.out/core/mobx/form_model.d.ts +9 -6
  3. package/.out/core/mobx/form_model.js +77 -42
  4. package/.out/core/mobx/specs/form_model.tests.js +80 -20
  5. package/.out/core/mobx/types.d.ts +4 -4
  6. package/.out/core/props.d.ts +2 -0
  7. package/.out/index.d.ts +0 -1
  8. package/.out/index.js +0 -1
  9. package/.out/mantine/create_field_view.d.ts +20 -0
  10. package/.out/mantine/create_field_view.js +54 -0
  11. package/.out/mantine/create_list.js +3 -2
  12. package/.out/mantine/hooks.d.ts +4 -1
  13. package/.out/mantine/hooks.js +14 -2
  14. package/.out/mantine/specs/field_view_hooks.stories.d.ts +12 -0
  15. package/.out/mantine/specs/field_view_hooks.stories.js +61 -0
  16. package/.out/mantine/specs/field_view_hooks.tests.d.ts +1 -0
  17. package/.out/mantine/specs/field_view_hooks.tests.js +12 -0
  18. package/.out/tsconfig.tsbuildinfo +1 -1
  19. package/.turbo/turbo-build.log +8 -8
  20. package/.turbo/turbo-check-types.log +1 -1
  21. package/core/mobx/field_adapter_builder.ts +2 -2
  22. package/core/mobx/form_model.ts +89 -47
  23. package/core/mobx/specs/form_model.tests.ts +131 -11
  24. package/core/mobx/types.ts +4 -4
  25. package/core/props.ts +4 -0
  26. package/dist/index.cjs +165 -89
  27. package/dist/index.d.cts +45 -40
  28. package/dist/index.d.ts +45 -40
  29. package/dist/index.js +162 -81
  30. package/index.ts +0 -1
  31. package/mantine/create_field_view.tsx +94 -0
  32. package/mantine/create_list.tsx +9 -2
  33. package/mantine/hooks.tsx +19 -2
  34. package/mantine/specs/__snapshots__/field_view_hooks.tests.tsx.snap +153 -0
  35. package/mantine/specs/field_view_hooks.stories.tsx +112 -0
  36. package/mantine/specs/field_view_hooks.tests.tsx +15 -0
  37. package/package.json +1 -1
  38. package/.out/mantine/field_view.d.ts +0 -18
  39. package/.out/mantine/field_view.js +0 -16
  40. package/mantine/field_view.tsx +0 -39
package/dist/index.js CHANGED
@@ -326,26 +326,28 @@ import {
326
326
  observable,
327
327
  runInAction
328
328
  } from "mobx";
329
- var _accessors_dec, _knownFields_dec, _fields_dec, _context_dec, _errors_dec, _fieldOverrides_dec, _value_dec, _init, _value, _fieldOverrides, _errors;
330
- _value_dec = [observable.ref], _fieldOverrides_dec = [observable.shallow], _errors_dec = [observable.shallow], _context_dec = [computed.struct], _fields_dec = [computed], _knownFields_dec = [computed], _accessors_dec = [computed];
329
+ var _accessors_dec, _knownFields_dec, _fields_dec, _errors_dec, _fieldOverrides_dec, _value_dec, _init, _value, _fieldOverrides, _errors;
330
+ _value_dec = [observable.ref], _fieldOverrides_dec = [observable.shallow], _errors_dec = [observable.shallow], _fields_dec = [computed], _knownFields_dec = [computed], _accessors_dec = [computed];
331
331
  var FormModel = class {
332
- constructor(type, value, adapters) {
332
+ constructor(type, originalValue, adapters, mode) {
333
333
  this.type = type;
334
+ this.originalValue = originalValue;
334
335
  this.adapters = adapters;
336
+ this.mode = mode;
335
337
  __runInitializers(_init, 5, this);
336
338
  __privateAdd(this, _value, __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
337
339
  __privateAdd(this, _fieldOverrides, __runInitializers(_init, 12, this)), __runInitializers(_init, 15, this);
338
340
  __privateAdd(this, _errors, __runInitializers(_init, 16, this, {})), __runInitializers(_init, 19, this);
339
341
  __publicField(this, "flattenedTypeDefs");
340
- this.value = mobxCopy(type, value);
342
+ this.value = mobxCopy(type, originalValue);
341
343
  this.flattenedTypeDefs = flattenTypesOfType(type);
342
- const contextValue = this.toContext(value);
343
344
  const conversions = flattenValueTo(
344
345
  type,
345
346
  this.value,
346
347
  () => {
347
348
  },
348
- (_t, value2, _setter, typePath, valuePath) => {
349
+ (_t, fieldValue, _setter, typePath, valuePath) => {
350
+ const contextValue = this.toContext(originalValue, valuePath);
349
351
  const adapter2 = this.adapters[typePath];
350
352
  if (adapter2 == null) {
351
353
  return;
@@ -357,15 +359,22 @@ var FormModel = class {
357
359
  if (revert == null) {
358
360
  return;
359
361
  }
360
- return convert(value2, valuePath, contextValue);
362
+ return convert(fieldValue, valuePath, contextValue);
361
363
  }
362
364
  );
363
365
  this.fieldOverrides = map(conversions, function(_k, v) {
364
366
  return v && [v.value];
365
367
  });
366
368
  }
367
- get context() {
368
- return this.toContext(this.value);
369
+ get forceMutableFields() {
370
+ switch (this.mode) {
371
+ case "create":
372
+ return true;
373
+ case "edit":
374
+ return false;
375
+ default:
376
+ return this.mode;
377
+ }
369
378
  }
370
379
  get fields() {
371
380
  return new Proxy(
@@ -426,6 +435,7 @@ var FormModel = class {
426
435
  const fieldOverride = this.fieldOverrides[valuePath];
427
436
  const accessor = this.getAccessorForValuePath(valuePath);
428
437
  const fieldTypeDef = this.flattenedTypeDefs[typePath];
438
+ const context = this.toContext(this.value, valuePath);
429
439
  const {
430
440
  value,
431
441
  required,
@@ -434,17 +444,17 @@ var FormModel = class {
434
444
  accessor != null ? accessor.value : fieldTypeDef != null ? mobxCopy(
435
445
  fieldTypeDef,
436
446
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
437
- create(valuePath, this.context)
438
- ) : create(valuePath, this.context),
447
+ create(valuePath, context)
448
+ ) : create(valuePath, context),
439
449
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
440
450
  valuePath,
441
- this.context
451
+ context
442
452
  );
443
453
  const error = this.errors[valuePath];
444
454
  return {
445
455
  value: fieldOverride != null ? fieldOverride[0] : value,
446
456
  error,
447
- readonly,
457
+ readonly: readonly && !this.forceMutableFields,
448
458
  required
449
459
  };
450
460
  }
@@ -496,7 +506,9 @@ var FormModel = class {
496
506
  const element = elementValue != null ? elementValue[0] : elementAdapter.create(
497
507
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
498
508
  elementTypePath,
499
- this.context
509
+ // TODO what can we use for the value path here?
510
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
511
+ this.toContext(this.value, valuePath)
500
512
  );
501
513
  const originalList = accessor.value;
502
514
  const newList = [
@@ -614,7 +626,7 @@ var FormModel = class {
614
626
  internalSetFieldValue(valuePath, value, displayValidation) {
615
627
  const { revert } = this.getAdapterForValuePath(valuePath);
616
628
  assertExists(revert, "setting value not supported {}", valuePath);
617
- const conversion = revert(value, valuePath, this.context);
629
+ const conversion = revert(value, valuePath, this.toContext(this.value, valuePath));
618
630
  const accessor = this.getAccessorForValuePath(valuePath);
619
631
  return runInAction(() => {
620
632
  this.fieldOverrides[valuePath] = [value];
@@ -654,10 +666,11 @@ var FormModel = class {
654
666
  convert,
655
667
  create
656
668
  } = adapter2;
657
- const value = create(valuePath, this.context);
669
+ const context = this.toContext(this.value, valuePath);
670
+ const value = create(valuePath, context);
658
671
  const {
659
672
  value: displayValue
660
- } = convert(value, valuePath, this.context);
673
+ } = convert(value, valuePath, context);
661
674
  const key = valuePath;
662
675
  runInAction(() => {
663
676
  this.fieldOverrides[key] = [displayValue];
@@ -683,13 +696,14 @@ var FormModel = class {
683
696
  } = this.getAdapterForValuePath(valuePath);
684
697
  const fieldOverride = this.fieldOverrides[valuePath];
685
698
  const accessor = this.getAccessorForValuePath(valuePath);
699
+ const context = this.toContext(this.value, valuePath);
686
700
  const {
687
701
  value: storedValue
688
702
  } = convert(
689
- accessor != null ? accessor.value : create(valuePath, this.context),
703
+ accessor != null ? accessor.value : create(valuePath, context),
690
704
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
691
705
  valuePath,
692
- this.context
706
+ context
693
707
  );
694
708
  const value = fieldOverride != null ? fieldOverride[0] : storedValue;
695
709
  const dirty = storedValue !== value;
@@ -697,12 +711,12 @@ var FormModel = class {
697
711
  if (ignoreDefaultValue) {
698
712
  const {
699
713
  value: defaultDisplayValue
700
- } = convert(create(valuePath, this.context), valuePath, this.context);
714
+ } = convert(create(valuePath, context), valuePath, context);
701
715
  if (defaultDisplayValue === value) {
702
716
  return true;
703
717
  }
704
718
  }
705
- const conversion = revert(value, valuePath, this.context);
719
+ const conversion = revert(value, valuePath, context);
706
720
  return runInAction(() => {
707
721
  switch (conversion.type) {
708
722
  case 1 /* Failure */:
@@ -722,10 +736,11 @@ var FormModel = class {
722
736
  }
723
737
  });
724
738
  }
725
- validateAll() {
739
+ validateAll(force = this.mode === "create") {
726
740
  const accessors = toArray(this.accessors).toSorted(function([a], [b]) {
727
741
  return a.length - b.length;
728
742
  });
743
+ const flattenedOriginalValues = flattenValuesOfType(this.type, this.originalValue);
729
744
  return runInAction(() => {
730
745
  return accessors.reduce(
731
746
  (success, [
@@ -745,28 +760,38 @@ var FormModel = class {
745
760
  return success;
746
761
  }
747
762
  const fieldOverride = this.fieldOverrides[adapterPath];
763
+ const context = this.toContext(this.value, valuePath);
748
764
  const {
749
765
  value: storedValue
750
- } = convert(accessor.value, valuePath, this.context);
766
+ } = convert(accessor.value, valuePath, context);
751
767
  const value = fieldOverride != null ? fieldOverride[0] : storedValue;
752
768
  const dirty = fieldOverride != null && fieldOverride[0] !== storedValue;
753
- const conversion = revert(value, valuePath, this.context);
754
- switch (conversion.type) {
755
- case 1 /* Failure */:
756
- this.errors[adapterPath] = conversion.error;
757
- if (conversion.value != null && dirty) {
758
- accessor.set(conversion.value[0]);
759
- }
760
- return false;
761
- case 0 /* Success */:
762
- if (dirty) {
763
- accessor.set(conversion.value);
764
- }
765
- delete this.errors[adapterPath];
766
- return success;
767
- default:
768
- throw new UnreachableError2(conversion);
769
+ const needsValidation = force || !(valuePath in flattenedOriginalValues) || storedValue !== convert(
770
+ flattenedOriginalValues[valuePath],
771
+ valuePath,
772
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
773
+ this.toContext(this.originalValue, valuePath)
774
+ ).value;
775
+ if (needsValidation) {
776
+ const conversion = revert(value, valuePath, context);
777
+ switch (conversion.type) {
778
+ case 1 /* Failure */:
779
+ this.errors[adapterPath] = conversion.error;
780
+ if (conversion.value != null && dirty) {
781
+ accessor.set(conversion.value[0]);
782
+ }
783
+ return false;
784
+ case 0 /* Success */:
785
+ if (dirty) {
786
+ accessor.set(conversion.value);
787
+ }
788
+ delete this.errors[adapterPath];
789
+ return success;
790
+ default:
791
+ throw new UnreachableError2(conversion);
792
+ }
769
793
  }
794
+ return success;
770
795
  },
771
796
  true
772
797
  );
@@ -780,7 +805,6 @@ _errors = new WeakMap();
780
805
  __decorateElement(_init, 4, "value", _value_dec, FormModel, _value);
781
806
  __decorateElement(_init, 4, "fieldOverrides", _fieldOverrides_dec, FormModel, _fieldOverrides);
782
807
  __decorateElement(_init, 4, "errors", _errors_dec, FormModel, _errors);
783
- __decorateElement(_init, 2, "context", _context_dec, FormModel);
784
808
  __decorateElement(_init, 2, "fields", _fields_dec, FormModel);
785
809
  __decorateElement(_init, 2, "knownFields", _knownFields_dec, FormModel);
786
810
  __decorateElement(_init, 2, "accessors", _accessors_dec, FormModel);
@@ -1149,34 +1173,6 @@ function DefaultErrorRenderer({
1149
1173
  return JSON.stringify(error);
1150
1174
  }
1151
1175
 
1152
- // mantine/field_view.tsx
1153
- import { Observer } from "mobx-react";
1154
-
1155
- // util/empty.tsx
1156
- function Empty() {
1157
- return null;
1158
- }
1159
-
1160
- // mantine/field_view.tsx
1161
- import { jsx } from "react/jsx-runtime";
1162
- function FieldView({
1163
- fields,
1164
- valuePath,
1165
- children
1166
- }) {
1167
- return /* @__PURE__ */ jsx(Observer, { children: () => {
1168
- const {
1169
- value,
1170
- error
1171
- } = fields[valuePath];
1172
- return children({
1173
- value,
1174
- error,
1175
- ErrorSink: Empty
1176
- });
1177
- } });
1178
- }
1179
-
1180
1176
  // mantine/hooks.tsx
1181
1177
  import {
1182
1178
  Checkbox as CheckboxImpl,
@@ -1203,12 +1199,12 @@ import {
1203
1199
  forwardRef,
1204
1200
  useMemo
1205
1201
  } from "react";
1206
- import { jsx as jsx2 } from "react/jsx-runtime";
1202
+ import { jsx } from "react/jsx-runtime";
1207
1203
  function createSimplePartialComponent(Component, curriedProps) {
1208
1204
  return forwardRef(
1209
1205
  function(exposedProps, ref) {
1210
1206
  const C = Component;
1211
- return /* @__PURE__ */ jsx2(
1207
+ return /* @__PURE__ */ jsx(
1212
1208
  C,
1213
1209
  __spreadValues(__spreadValues({
1214
1210
  ref
@@ -1248,7 +1244,7 @@ function createPartialComponent(Component, curriedPropsSource, additionalPropKey
1248
1244
  ]
1249
1245
  );
1250
1246
  const curriedProps = curriedPropsSource(additionalProps);
1251
- return /* @__PURE__ */ jsx2(
1247
+ return /* @__PURE__ */ jsx(
1252
1248
  C,
1253
1249
  __spreadValues(__spreadValues({
1254
1250
  ref
@@ -1315,7 +1311,7 @@ function createUnsafePartialObserverComponent(Component, curriedPropsSource, add
1315
1311
  ]
1316
1312
  );
1317
1313
  const curriedProps = curriedPropsSource(additionalProps);
1318
- return /* @__PURE__ */ jsx2(
1314
+ return /* @__PURE__ */ jsx(
1319
1315
  C,
1320
1316
  __spreadValues(__spreadValues({
1321
1317
  ref
@@ -1346,7 +1342,7 @@ function usePartialObserverComponent(curriedPropsSource, deps, Component, additi
1346
1342
  }
1347
1343
 
1348
1344
  // mantine/create_checkbox.tsx
1349
- import { jsx as jsx3 } from "react/jsx-runtime";
1345
+ import { jsx as jsx2 } from "react/jsx-runtime";
1350
1346
  function createCheckbox(valuePath, Checkbox) {
1351
1347
  const onChange = (e) => {
1352
1348
  var _a;
@@ -1383,7 +1379,7 @@ function createCheckbox(valuePath, Checkbox) {
1383
1379
  checked: value,
1384
1380
  disabled: readonly,
1385
1381
  required,
1386
- error: error && /* @__PURE__ */ jsx3(ErrorRenderer, { error }),
1382
+ error: error && /* @__PURE__ */ jsx2(ErrorRenderer, { error }),
1387
1383
  onChange,
1388
1384
  onFocus,
1389
1385
  onBlur,
@@ -1397,6 +1393,80 @@ function createCheckbox(valuePath, Checkbox) {
1397
1393
  );
1398
1394
  }
1399
1395
 
1396
+ // mantine/create_field_view.tsx
1397
+ import { Observer } from "mobx-react";
1398
+ import {
1399
+ useCallback as useCallback2
1400
+ } from "react";
1401
+
1402
+ // util/empty.tsx
1403
+ function Empty() {
1404
+ return null;
1405
+ }
1406
+
1407
+ // mantine/create_field_view.tsx
1408
+ import { jsx as jsx3 } from "react/jsx-runtime";
1409
+ function FieldView({
1410
+ valuePath,
1411
+ form,
1412
+ children
1413
+ }) {
1414
+ const onFocus = useCallback2(() => {
1415
+ var _a;
1416
+ (_a = form.onFieldFocus) == null ? void 0 : _a.call(form, valuePath);
1417
+ }, [
1418
+ form,
1419
+ valuePath
1420
+ ]);
1421
+ const onBlur = useCallback2(() => {
1422
+ var _a;
1423
+ (_a = form.onFieldBlur) == null ? void 0 : _a.call(form, valuePath);
1424
+ }, [
1425
+ form,
1426
+ valuePath
1427
+ ]);
1428
+ const onValueChange = useCallback2((value) => {
1429
+ var _a;
1430
+ (_a = form.onFieldValueChange) == null ? void 0 : _a.call(form, valuePath, value);
1431
+ }, [
1432
+ form,
1433
+ valuePath
1434
+ ]);
1435
+ const onSubmit = useCallback2(() => {
1436
+ var _a;
1437
+ (_a = form.onFieldSubmit) == null ? void 0 : _a.call(form, valuePath);
1438
+ }, [
1439
+ form,
1440
+ valuePath
1441
+ ]);
1442
+ return /* @__PURE__ */ jsx3(Observer, { children: () => {
1443
+ const {
1444
+ value,
1445
+ error
1446
+ } = form.fields[valuePath];
1447
+ return children({
1448
+ value,
1449
+ error,
1450
+ ErrorSink: Empty,
1451
+ onFocus,
1452
+ onBlur,
1453
+ onValueChange,
1454
+ onSubmit
1455
+ });
1456
+ } });
1457
+ }
1458
+ function createFieldView(valuePath) {
1459
+ return (props) => {
1460
+ return /* @__PURE__ */ jsx3(
1461
+ FieldView,
1462
+ __spreadValues({
1463
+ form: this,
1464
+ valuePath
1465
+ }, props)
1466
+ );
1467
+ };
1468
+ }
1469
+
1400
1470
  // mantine/create_fields_view.tsx
1401
1471
  import {
1402
1472
  jsonPathPrefix,
@@ -1468,13 +1538,13 @@ function createFieldsView(valuePath, FieldsView, observableProps) {
1468
1538
  // mantine/create_form.tsx
1469
1539
  import { observer as observer3 } from "mobx-react";
1470
1540
  import {
1471
- useCallback as useCallback2
1541
+ useCallback as useCallback3
1472
1542
  } from "react";
1473
1543
  import { jsx as jsx5 } from "react/jsx-runtime";
1474
1544
  function createForm(valuePath, Form, observableProps) {
1475
1545
  return observer3((props) => {
1476
1546
  const { value } = observableProps.fields[valuePath];
1477
- const onValueChange = useCallback2((value2) => {
1547
+ const onValueChange = useCallback3((value2) => {
1478
1548
  observableProps.onFieldValueChange(valuePath, value2);
1479
1549
  }, []);
1480
1550
  return /* @__PURE__ */ jsx5(
@@ -1488,7 +1558,10 @@ function createForm(valuePath, Form, observableProps) {
1488
1558
  }
1489
1559
 
1490
1560
  // mantine/create_list.tsx
1491
- import { Fragment, jsx as jsx6 } from "react/jsx-runtime";
1561
+ import {
1562
+ Fragment
1563
+ } from "react";
1564
+ import { Fragment as Fragment2, jsx as jsx6 } from "react/jsx-runtime";
1492
1565
  function createList(valuePath, List) {
1493
1566
  const propSource = () => {
1494
1567
  const values = [...this.fields[valuePath].value];
@@ -1504,9 +1577,9 @@ function DefaultList({
1504
1577
  listPath,
1505
1578
  children
1506
1579
  }) {
1507
- return /* @__PURE__ */ jsx6(Fragment, { children: values.map(function(value, index) {
1580
+ return /* @__PURE__ */ jsx6(Fragment2, { children: values.map(function(value, index) {
1508
1581
  const valuePath = `${listPath}.${index}`;
1509
- return children(valuePath, value, index);
1582
+ return /* @__PURE__ */ jsx6(Fragment, { children: children(valuePath, value, index) }, valuePath);
1510
1583
  }) });
1511
1584
  }
1512
1585
 
@@ -1705,12 +1778,13 @@ function useMantineFormFields({
1705
1778
  onFieldFocus,
1706
1779
  onFieldSubmit,
1707
1780
  fields
1781
+ // should use FieldView rather than observing fields directly from here
1708
1782
  }) {
1709
1783
  const form = useMemo2(
1710
1784
  function() {
1711
1785
  return new MantineFormImpl(fields);
1712
1786
  },
1713
- // handled separately below
1787
+ // fields handled separately below
1714
1788
  // eslint-disable-next-line react-hooks/exhaustive-deps
1715
1789
  []
1716
1790
  );
@@ -1773,6 +1847,9 @@ var MantineFormImpl = class {
1773
1847
  __publicField(this, "listCache", new Cache(
1774
1848
  createList.bind(this)
1775
1849
  ));
1850
+ __publicField(this, "fieldViewCache", new Cache(
1851
+ createFieldView.bind(this)
1852
+ ));
1776
1853
  __publicField(this, "fieldsViewCache", new Cache(
1777
1854
  createFieldsView.bind(this)
1778
1855
  ));
@@ -1840,6 +1917,11 @@ var MantineFormImpl = class {
1840
1917
  DefaultList
1841
1918
  );
1842
1919
  }
1920
+ fieldView(valuePath) {
1921
+ return this.fieldViewCache.retrieveOrCreate(
1922
+ valuePath
1923
+ );
1924
+ }
1843
1925
  fieldsView(valuePath, FieldsView) {
1844
1926
  return this.fieldsViewCache.retrieveOrCreate(
1845
1927
  valuePath,
@@ -1907,7 +1989,6 @@ export {
1907
1989
  AbstractSelectValueTypeConverter,
1908
1990
  DefaultErrorRenderer,
1909
1991
  Empty,
1910
- FieldView,
1911
1992
  FormModel,
1912
1993
  IntegerToStringConverter,
1913
1994
  NullableToBooleanConverter,
package/index.ts CHANGED
@@ -17,7 +17,6 @@ export * from './field_converters/trimming_string_converter'
17
17
  export * from './field_converters/validating_converter'
18
18
  export * from './field_value_factories/prototyping_field_value_factory'
19
19
  export * from './mantine/error_renderer'
20
- export * from './mantine/field_view'
21
20
  export * from './mantine/hooks'
22
21
  export * from './types/error_of_field'
23
22
  export * from './types/field'
@@ -0,0 +1,94 @@
1
+ import { Observer } from 'mobx-react'
2
+ import {
3
+ type ComponentType,
4
+ useCallback,
5
+ } from 'react'
6
+ import { type AllFieldsOfFields } from 'types/all_fields_of_fields'
7
+ import { type ErrorOfField } from 'types/error_of_field'
8
+ import { type Fields } from 'types/field'
9
+ import { type ValueTypeOfField } from 'types/value_type_of_field'
10
+ import { Empty } from 'util/empty'
11
+ import { type MantineForm } from './types'
12
+
13
+ export type FieldViewProps<F extends Fields, K extends keyof F> = {
14
+ children: (props: {
15
+ value: ValueTypeOfField<F[K]>,
16
+ error: ErrorOfField<F[K]> | undefined,
17
+ ErrorSink: ComponentType<{ error: ErrorOfField<F[K]> }>,
18
+ onFocus: () => void,
19
+ onBlur: () => void,
20
+ onValueChange: (v: ValueTypeOfField<F[K]>) => void,
21
+ onSubmit: () => void,
22
+ }) => JSX.Element,
23
+ }
24
+
25
+ /**
26
+ * Displays current value and error of a field
27
+ */
28
+ function FieldView<F extends Fields, K extends keyof F>({
29
+ valuePath,
30
+ form,
31
+ children,
32
+ }: FieldViewProps<F, K> & {
33
+ valuePath: K,
34
+ form: MantineForm<F>,
35
+ }) {
36
+ const onFocus = useCallback(() => {
37
+ form.onFieldFocus?.(valuePath)
38
+ }, [
39
+ form,
40
+ valuePath,
41
+ ])
42
+ const onBlur = useCallback(() => {
43
+ form.onFieldBlur?.(valuePath)
44
+ }, [
45
+ form,
46
+ valuePath,
47
+ ])
48
+ const onValueChange = useCallback((value: ValueTypeOfField<F[K]>) => {
49
+ form.onFieldValueChange?.(valuePath, value)
50
+ }, [
51
+ form,
52
+ valuePath,
53
+ ])
54
+ const onSubmit = useCallback(() => {
55
+ form.onFieldSubmit?.(valuePath)
56
+ }, [
57
+ form,
58
+ valuePath,
59
+ ])
60
+ return (
61
+ <Observer>
62
+ {() => {
63
+ const {
64
+ value,
65
+ error,
66
+ } = form.fields[valuePath]
67
+ return children({
68
+ value,
69
+ error,
70
+ ErrorSink: Empty,
71
+ onFocus,
72
+ onBlur,
73
+ onValueChange,
74
+ onSubmit,
75
+ })
76
+ }}
77
+ </Observer>
78
+ )
79
+ }
80
+
81
+ export function createFieldView<
82
+ F extends Fields,
83
+ K extends keyof AllFieldsOfFields<F>,
84
+ >(this: MantineForm<F>, valuePath: K): ComponentType<FieldViewProps<F, K>> {
85
+ return (props: FieldViewProps<F, K>) => {
86
+ return (
87
+ <FieldView
88
+ form={this}
89
+ valuePath={valuePath}
90
+ {...props}
91
+ />
92
+ )
93
+ }
94
+ }
@@ -1,5 +1,8 @@
1
1
  import { type ElementOfArray } from '@strictly/base'
2
- import { type ComponentType } from 'react'
2
+ import {
3
+ type ComponentType,
4
+ Fragment,
5
+ } from 'react'
3
6
  import { type Fields } from 'types/field'
4
7
  import { type ListFieldsOfFields } from 'types/list_fields_of_fields'
5
8
  import { type ValueTypeOfField } from 'types/value_type_of_field'
@@ -54,7 +57,11 @@ export function DefaultList<
54
57
  <>
55
58
  {values.map(function (value, index) {
56
59
  const valuePath: `${ListPath}.${number}` = `${listPath}.${index}`
57
- return children(valuePath, value, index)
60
+ return (
61
+ <Fragment key={valuePath}>
62
+ {children(valuePath, value, index)}
63
+ </Fragment>
64
+ )
58
65
  })}
59
66
  </>
60
67
  )
package/mantine/hooks.tsx CHANGED
@@ -44,6 +44,10 @@ import {
44
44
  createCheckbox,
45
45
  type SuppliedCheckboxProps,
46
46
  } from './create_checkbox'
47
+ import {
48
+ createFieldView,
49
+ type FieldViewProps,
50
+ } from './create_field_view'
47
51
  import {
48
52
  createFieldsView,
49
53
  type FieldsView,
@@ -93,12 +97,13 @@ export function useMantineFormFields<
93
97
  onFieldFocus,
94
98
  onFieldSubmit,
95
99
  fields,
96
- }: FieldsViewProps<F>): MantineFormImpl<F> {
100
+ // should use FieldView rather than observing fields directly from here
101
+ }: FieldsViewProps<F>): Omit<MantineFormImpl<F>, 'fields'> {
97
102
  const form = useMemo(
98
103
  function () {
99
104
  return new MantineFormImpl(fields)
100
105
  },
101
- // handled separately below
106
+ // fields handled separately below
102
107
  // eslint-disable-next-line react-hooks/exhaustive-deps
103
108
  [],
104
109
  )
@@ -184,6 +189,12 @@ class MantineFormImpl<
184
189
  > = new Cache(
185
190
  createList.bind(this),
186
191
  )
192
+ private readonly fieldViewCache: Cache<
193
+ [keyof AllFieldsOfFields<F>],
194
+ ComponentType<FieldViewProps<F, Exclude<keyof AllFieldsOfFields<F>, number | symbol>>>
195
+ > = new Cache(
196
+ createFieldView.bind(this),
197
+ )
187
198
  private readonly fieldsViewCache: Cache<
188
199
  // the cache cannot reference keys, so we just use any
189
200
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -392,6 +403,12 @@ class MantineFormImpl<
392
403
  >
393
404
  }
394
405
 
406
+ fieldView<K extends keyof AllFieldsOfFields<F>>(valuePath: K): ComponentType<FieldViewProps<F, K>> {
407
+ return this.fieldViewCache.retrieveOrCreate(
408
+ valuePath,
409
+ )
410
+ }
411
+
395
412
  fieldsView<
396
413
  K extends keyof AllFieldsOfFields<F>,
397
414
  P extends FieldsViewProps<Fields> = FieldsViewProps<SubFormFields<F, K>>,