@uniform-ts/core 0.0.7 → 0.0.9

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/dist/index.js CHANGED
@@ -657,6 +657,24 @@ function SelectField({
657
657
  }
658
658
  );
659
659
  }
660
+ function useConditionalFields(fields, control, scopeName) {
661
+ const allValues = reactHookForm.useWatch({ control });
662
+ const scopedValues = reactHookForm.useWatch({ control, name: scopeName });
663
+ const values = scopeName ? scopedValues : allValues;
664
+ return React3.useMemo(() => {
665
+ return fields.filter((field) => {
666
+ if (field.meta.hidden) return false;
667
+ if (typeof field.meta.condition === "function") {
668
+ return field.meta.condition(values);
669
+ }
670
+ return true;
671
+ }).sort((a, b) => {
672
+ const orderA = typeof a.meta.order === "number" ? a.meta.order : Infinity;
673
+ const orderB = typeof b.meta.order === "number" ? b.meta.order : Infinity;
674
+ return orderA - orderB;
675
+ });
676
+ }, [fields, values]);
677
+ }
660
678
  function ObjectField({
661
679
  field,
662
680
  control,
@@ -665,7 +683,7 @@ function ObjectField({
665
683
  shouldUnregister
666
684
  }) {
667
685
  const { classNames, layout } = useAutoFormContext();
668
- const children = field.children;
686
+ const children = useConditionalFields(field.children, control, namePrefix);
669
687
  const content = children.map((child, idx) => /* @__PURE__ */ jsxRuntime.jsx(
670
688
  FieldRenderer,
671
689
  {
@@ -720,6 +738,124 @@ function getDefaultValue(field) {
720
738
  return void 0;
721
739
  }
722
740
  }
741
+
742
+ // src/utils/reindexDynamicMeta.ts
743
+ function buildKeyPattern(arrayName) {
744
+ return new RegExp(`^${escapeRegExp(arrayName)}\\.(\\d+)\\.(.+)$`);
745
+ }
746
+ function escapeRegExp(str) {
747
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
748
+ }
749
+ function reindexDynamicMeta(dynamicMeta, arrayName, mutation) {
750
+ const pattern = buildKeyPattern(arrayName);
751
+ const matched = [];
752
+ const result = {};
753
+ for (const [key, value] of Object.entries(dynamicMeta)) {
754
+ const match = pattern.exec(key);
755
+ if (match) {
756
+ matched.push({
757
+ key,
758
+ index: parseInt(match[1], 10),
759
+ childField: match[2],
760
+ value
761
+ });
762
+ } else {
763
+ result[key] = value;
764
+ }
765
+ }
766
+ if (!isValidMutation(mutation)) {
767
+ return dynamicMeta;
768
+ }
769
+ switch (mutation.type) {
770
+ case "remove":
771
+ applyRemove(matched, mutation.index, arrayName, result);
772
+ break;
773
+ case "move":
774
+ applyMove(matched, mutation.from, mutation.to, arrayName, result);
775
+ break;
776
+ case "duplicate":
777
+ applyDuplicate(matched, mutation.index, arrayName, result);
778
+ break;
779
+ case "add":
780
+ applyAdd(matched, mutation.index, arrayName, result);
781
+ break;
782
+ }
783
+ return result;
784
+ }
785
+ function isValidMutation(mutation, _entries) {
786
+ switch (mutation.type) {
787
+ case "remove":
788
+ return mutation.index >= 0;
789
+ case "move":
790
+ return mutation.from >= 0 && mutation.to >= 0;
791
+ case "duplicate":
792
+ return mutation.index >= 0;
793
+ case "add":
794
+ return mutation.index >= 0;
795
+ }
796
+ }
797
+ function buildKey(arrayName, index, childField) {
798
+ return `${arrayName}.${index}.${childField}`;
799
+ }
800
+ function applyRemove(entries, removedIndex, arrayName, result) {
801
+ for (const entry of entries) {
802
+ if (entry.index === removedIndex) {
803
+ continue;
804
+ } else if (entry.index > removedIndex) {
805
+ result[buildKey(arrayName, entry.index - 1, entry.childField)] = entry.value;
806
+ } else {
807
+ result[buildKey(arrayName, entry.index, entry.childField)] = entry.value;
808
+ }
809
+ }
810
+ }
811
+ function applyMove(entries, from, to, arrayName, result) {
812
+ if (from === to) {
813
+ for (const entry of entries) {
814
+ result[buildKey(arrayName, entry.index, entry.childField)] = entry.value;
815
+ }
816
+ return;
817
+ }
818
+ for (const entry of entries) {
819
+ let newIndex;
820
+ if (entry.index === from) {
821
+ newIndex = to;
822
+ } else if (from < to) {
823
+ if (entry.index > from && entry.index <= to) {
824
+ newIndex = entry.index - 1;
825
+ } else {
826
+ newIndex = entry.index;
827
+ }
828
+ } else {
829
+ if (entry.index >= to && entry.index < from) {
830
+ newIndex = entry.index + 1;
831
+ } else {
832
+ newIndex = entry.index;
833
+ }
834
+ }
835
+ result[buildKey(arrayName, newIndex, entry.childField)] = entry.value;
836
+ }
837
+ }
838
+ function applyDuplicate(entries, sourceIndex, arrayName, result) {
839
+ for (const entry of entries) {
840
+ if (entry.index === sourceIndex) {
841
+ result[buildKey(arrayName, entry.index, entry.childField)] = entry.value;
842
+ result[buildKey(arrayName, sourceIndex + 1, entry.childField)] = entry.value;
843
+ } else if (entry.index > sourceIndex) {
844
+ result[buildKey(arrayName, entry.index + 1, entry.childField)] = entry.value;
845
+ } else {
846
+ result[buildKey(arrayName, entry.index, entry.childField)] = entry.value;
847
+ }
848
+ }
849
+ }
850
+ function applyAdd(entries, newIndex, arrayName, result) {
851
+ for (const entry of entries) {
852
+ if (entry.index >= newIndex) {
853
+ result[buildKey(arrayName, entry.index + 1, entry.childField)] = entry.value;
854
+ } else {
855
+ result[buildKey(arrayName, entry.index, entry.childField)] = entry.value;
856
+ }
857
+ }
858
+ }
723
859
  function getRowSummary(row, itemConfig, index, itemSummary) {
724
860
  if (itemConfig.type === "object") {
725
861
  for (const child of itemConfig.children) {
@@ -732,8 +868,43 @@ function getRowSummary(row, itemConfig, index, itemSummary) {
732
868
  }
733
869
  return itemSummary?.(index) ?? `Item ${index + 1}`;
734
870
  }
871
+ function bindRowIndexToItemConfig(itemConfig, rowIndex) {
872
+ if (itemConfig.type !== "object") return itemConfig;
873
+ const children = itemConfig.children.map((child) => {
874
+ if (!child.meta.onChange) return child;
875
+ const originalOnChange = child.meta.onChange;
876
+ return {
877
+ ...child,
878
+ meta: {
879
+ ...child.meta,
880
+ onChange: (value, formMethods) => {
881
+ void originalOnChange(value, formMethods, rowIndex);
882
+ }
883
+ }
884
+ };
885
+ });
886
+ return { ...itemConfig, children };
887
+ }
888
+ function applyRowDynamicMeta(itemConfig, rowOverrides) {
889
+ if (itemConfig.type !== "object") return itemConfig;
890
+ const children = itemConfig.children.map((child) => {
891
+ const override = rowOverrides[child.name];
892
+ if (!override) return child;
893
+ const { options, label, ...metaOverrides } = override;
894
+ let updated = {
895
+ ...child,
896
+ ...label !== void 0 ? { label } : {},
897
+ meta: { ...child.meta, ...metaOverrides }
898
+ };
899
+ if (options !== void 0 && updated.type === "select") {
900
+ updated = { ...updated, options };
901
+ }
902
+ return updated;
903
+ });
904
+ return { ...itemConfig, children };
905
+ }
735
906
  function ArrayField({ field, control, effectiveName }) {
736
- const { classNames, layout, labels } = useAutoFormContext();
907
+ const { classNames, layout, labels, setDynamicMeta } = useAutoFormContext();
737
908
  const {
738
909
  fields: rows,
739
910
  append,
@@ -779,9 +950,10 @@ function ArrayField({ field, control, effectiveName }) {
779
950
  } = layout.arrayButtons;
780
951
  const ArrayFieldLayout = layout.arrayFieldLayout;
781
952
  const RowLayout = layout.arrayRowLayout;
953
+ const rowDynamicMeta = field._rowDynamicMeta;
782
954
  const renderedRows = rows.map((row, index) => {
783
955
  const isCollapsed = showCollapse && collapsed.has(index);
784
- const collapseButton = showCollapse ? /* @__PURE__ */ jsxRuntime.jsxs(
956
+ const collapseButton = showCollapse && CollapseBtn ? /* @__PURE__ */ jsxRuntime.jsxs(
785
957
  CollapseBtn,
786
958
  {
787
959
  type: "button",
@@ -805,29 +977,47 @@ function ArrayField({ field, control, effectiveName }) {
805
977
  ]
806
978
  }
807
979
  ) : null;
808
- const moveUpButton = showMove && rows.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(
980
+ const moveUpButton = showMove && rows.length > 1 && MoveUpBtn ? /* @__PURE__ */ jsxRuntime.jsx(
809
981
  MoveUpBtn,
810
982
  {
811
983
  type: "button",
812
984
  className: classNames.arrayMove,
813
- onClick: () => move(index, index - 1),
985
+ onClick: () => {
986
+ move(index, index - 1);
987
+ setDynamicMeta(
988
+ (prev) => reindexDynamicMeta(prev, effectiveName, {
989
+ type: "move",
990
+ from: index,
991
+ to: index - 1
992
+ })
993
+ );
994
+ },
814
995
  disabled: index === 0,
815
996
  "aria-label": labels.arrayAriaMoveUp?.(index) ?? `Move item ${index + 1} up`,
816
997
  children: labels.arrayMoveUp ?? "\u2191"
817
998
  }
818
999
  ) : null;
819
- const moveDownButton = showMove && rows.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(
1000
+ const moveDownButton = showMove && rows.length > 1 && MoveDownBtn ? /* @__PURE__ */ jsxRuntime.jsx(
820
1001
  MoveDownBtn,
821
1002
  {
822
1003
  type: "button",
823
1004
  className: classNames.arrayMove,
824
- onClick: () => move(index, index + 1),
1005
+ onClick: () => {
1006
+ move(index, index + 1);
1007
+ setDynamicMeta(
1008
+ (prev) => reindexDynamicMeta(prev, effectiveName, {
1009
+ type: "move",
1010
+ from: index,
1011
+ to: index + 1
1012
+ })
1013
+ );
1014
+ },
825
1015
  disabled: index === rows.length - 1,
826
1016
  "aria-label": labels.arrayAriaMoveDown?.(index) ?? `Move item ${index + 1} down`,
827
1017
  children: labels.arrayMoveDown ?? "\u2193"
828
1018
  }
829
1019
  ) : null;
830
- const duplicateButton = showDuplicate && !atMax ? /* @__PURE__ */ jsxRuntime.jsx(
1020
+ const duplicateButton = showDuplicate && !atMax && DuplicateBtn ? /* @__PURE__ */ jsxRuntime.jsx(
831
1021
  DuplicateBtn,
832
1022
  {
833
1023
  type: "button",
@@ -837,26 +1027,43 @@ function ArrayField({ field, control, effectiveName }) {
837
1027
  Object.entries(row).filter(([k]) => k !== "id")
838
1028
  );
839
1029
  insert(index + 1, values);
1030
+ setDynamicMeta(
1031
+ (prev) => reindexDynamicMeta(prev, effectiveName, {
1032
+ type: "duplicate",
1033
+ index
1034
+ })
1035
+ );
840
1036
  },
841
1037
  "aria-label": labels.arrayAriaDuplicate?.(index) ?? `Duplicate item ${index + 1}`,
842
1038
  children: labels.arrayDuplicate ?? "Duplicate"
843
1039
  }
844
1040
  ) : null;
845
- const removeButton = /* @__PURE__ */ jsxRuntime.jsx(
1041
+ const removeButton = RemoveBtn ? /* @__PURE__ */ jsxRuntime.jsx(
846
1042
  RemoveBtn,
847
1043
  {
848
1044
  type: "button",
849
1045
  className: classNames.arrayRemove,
850
- onClick: () => remove(index),
1046
+ onClick: () => {
1047
+ remove(index);
1048
+ setDynamicMeta(
1049
+ (prev) => reindexDynamicMeta(prev, effectiveName, {
1050
+ type: "remove",
1051
+ index
1052
+ })
1053
+ );
1054
+ },
851
1055
  disabled: atMin,
852
1056
  "aria-label": labels.arrayAriaRemove?.(index) ?? `Remove item ${index + 1}`,
853
1057
  children: labels.arrayRemove ?? "Remove"
854
1058
  }
855
- );
1059
+ ) : null;
856
1060
  const fieldContent = !isCollapsed ? /* @__PURE__ */ jsxRuntime.jsx(
857
1061
  FieldRenderer,
858
1062
  {
859
- field: effectiveItemConfig,
1063
+ field: bindRowIndexToItemConfig(
1064
+ rowDynamicMeta?.[index] ? applyRowDynamicMeta(effectiveItemConfig, rowDynamicMeta[index]) : effectiveItemConfig,
1065
+ index
1066
+ ),
860
1067
  control,
861
1068
  namePrefix: `${effectiveName}.${index}`
862
1069
  }
@@ -878,16 +1085,25 @@ function ArrayField({ field, control, effectiveName }) {
878
1085
  row.id
879
1086
  );
880
1087
  });
881
- const addButton = /* @__PURE__ */ jsxRuntime.jsx(
1088
+ const addButton = AddBtn ? /* @__PURE__ */ jsxRuntime.jsx(
882
1089
  AddBtn,
883
1090
  {
884
1091
  type: "button",
885
1092
  className: classNames.arrayAdd,
886
1093
  disabled: atMax,
887
- onClick: () => append(getDefaultValue(itemConfig)),
1094
+ onClick: () => {
1095
+ const newIndex = rows.length;
1096
+ append(getDefaultValue(itemConfig));
1097
+ setDynamicMeta(
1098
+ (prev) => reindexDynamicMeta(prev, effectiveName, {
1099
+ type: "add",
1100
+ index: newIndex
1101
+ })
1102
+ );
1103
+ },
888
1104
  children: labels.arrayAdd ?? "Add"
889
1105
  }
890
- );
1106
+ ) : null;
891
1107
  const content = /* @__PURE__ */ jsxRuntime.jsx(
892
1108
  ArrayFieldLayout,
893
1109
  {
@@ -1049,22 +1265,6 @@ function FieldRenderer({
1049
1265
  }
1050
1266
  );
1051
1267
  }
1052
- function useConditionalFields(fields, control) {
1053
- const values = reactHookForm.useWatch({ control });
1054
- return React3.useMemo(() => {
1055
- return fields.filter((field) => {
1056
- if (field.meta.hidden) return false;
1057
- if (typeof field.meta.condition === "function") {
1058
- return field.meta.condition(values);
1059
- }
1060
- return true;
1061
- }).sort((a, b) => {
1062
- const orderA = typeof a.meta.order === "number" ? a.meta.order : Infinity;
1063
- const orderB = typeof b.meta.order === "number" ? b.meta.order : Infinity;
1064
- return orderA - orderB;
1065
- });
1066
- }, [fields, values]);
1067
- }
1068
1268
  function useSectionGrouping(fields) {
1069
1269
  return React3.useMemo(() => {
1070
1270
  const ungrouped = [];
@@ -1153,6 +1353,28 @@ function useLatestRef(value) {
1153
1353
  return ref;
1154
1354
  }
1155
1355
 
1356
+ // src/utils/createRowScopedContext.ts
1357
+ function createRowScopedContext(baseCtx, arrayName, rowIndex, itemFieldNames, getValues) {
1358
+ return {
1359
+ ...baseCtx,
1360
+ getValues,
1361
+ setFieldMeta: (field, meta) => {
1362
+ const fieldStr = field;
1363
+ const prefix = arrayName + ".";
1364
+ const childName = fieldStr.startsWith(prefix) ? fieldStr.slice(prefix.length) : fieldStr;
1365
+ if (itemFieldNames.has(childName)) {
1366
+ const qualifiedKey = `${arrayName}.${rowIndex}.${childName}`;
1367
+ baseCtx.setFieldMeta(
1368
+ qualifiedKey,
1369
+ meta
1370
+ );
1371
+ } else {
1372
+ baseCtx.setFieldMeta(field, meta);
1373
+ }
1374
+ }
1375
+ };
1376
+ }
1377
+
1156
1378
  // src/utils/fieldPipeline.ts
1157
1379
  function applyFieldOverrides(fields, overrides) {
1158
1380
  return fields.map((field) => {
@@ -1213,10 +1435,74 @@ function injectOnChangeHandlers(fields, uniForm, ctx, handlerKeys = new Set(uniF
1213
1435
  } else if (updated.type === "array") {
1214
1436
  const prefix = field.name + ".";
1215
1437
  const itemKeys = /* @__PURE__ */ new Set();
1438
+ const indexedKeys = /* @__PURE__ */ new Set();
1216
1439
  for (const key of handlerKeys) {
1217
- if (key.startsWith(prefix)) itemKeys.add(key.slice(prefix.length));
1440
+ if (key.startsWith(prefix)) {
1441
+ const remainder = key.slice(prefix.length);
1442
+ const indexMatch = remainder.match(/^(\d+)\.(.+)$/);
1443
+ if (indexMatch) {
1444
+ indexedKeys.add(remainder);
1445
+ itemKeys.add(indexMatch[2]);
1446
+ } else {
1447
+ itemKeys.add(remainder);
1448
+ }
1449
+ }
1218
1450
  }
1219
- if (itemKeys.size) {
1451
+ if (itemKeys.size && updated.itemConfig.type === "object") {
1452
+ const itemFieldNames = new Set(
1453
+ updated.itemConfig.children.map((c) => c.name)
1454
+ );
1455
+ const arrayName = field.name;
1456
+ const newChildren = updated.itemConfig.children.map((child) => {
1457
+ if (!itemKeys.has(child.name)) return child;
1458
+ const existingOnChange = child.meta.onChange;
1459
+ const rowAwareHandler = (value, formMethods, rowIndex) => {
1460
+ void existingOnChange?.(value, formMethods);
1461
+ const rowCtx = createRowScopedContext(
1462
+ ctx,
1463
+ arrayName,
1464
+ rowIndex,
1465
+ itemFieldNames,
1466
+ () => {
1467
+ const allValues = ctx.getValues();
1468
+ const arrayValues = allValues?.[arrayName];
1469
+ if (Array.isArray(arrayValues)) {
1470
+ return arrayValues[rowIndex] ?? {};
1471
+ }
1472
+ return {};
1473
+ }
1474
+ );
1475
+ void uniForm._fireHandler(
1476
+ `${arrayName}.${child.name}`,
1477
+ value,
1478
+ rowCtx
1479
+ );
1480
+ const indexedKey = `${rowIndex}.${child.name}`;
1481
+ if (indexedKeys.has(indexedKey)) {
1482
+ void uniForm._fireHandler(
1483
+ `${arrayName}.${indexedKey}`,
1484
+ value,
1485
+ rowCtx
1486
+ );
1487
+ }
1488
+ };
1489
+ return {
1490
+ ...child,
1491
+ meta: {
1492
+ ...child.meta,
1493
+ // Cast to FieldMeta onChange type — ArrayField's bindRowIndexToItemConfig
1494
+ // will call this with the rowIndex third argument at render time.
1495
+ onChange: rowAwareHandler
1496
+ }
1497
+ };
1498
+ });
1499
+ const newItemConfig = {
1500
+ ...updated.itemConfig,
1501
+ children: newChildren
1502
+ };
1503
+ if (newItemConfig !== updated.itemConfig)
1504
+ updated = { ...updated, itemConfig: newItemConfig };
1505
+ } else if (itemKeys.size) {
1220
1506
  const remappedUniForm = {
1221
1507
  _getWatchedFields: () => Array.from(itemKeys),
1222
1508
  _fireHandlers: (name, value, c) => uniForm._fireHandler(`${field.name}.${name}`, value, c)
@@ -1262,18 +1548,43 @@ function injectConditions(fields, conditions) {
1262
1548
  return updated;
1263
1549
  });
1264
1550
  }
1551
+ var ROW_KEY_PATTERN = /^(.+?)\.(\d+)\.(.+)$/;
1265
1552
  function applyDynamicMeta(fields, overrides) {
1266
1553
  if (!Object.keys(overrides).length) return fields;
1267
1554
  return fields.map((field) => {
1268
1555
  const override = overrides[field.name];
1269
- if (!override) return field;
1270
- const { options, label, ...metaOverrides } = override;
1271
- return {
1272
- ...field,
1273
- ...label !== void 0 ? { label } : {},
1274
- ...options !== void 0 ? { options } : {},
1275
- meta: { ...field.meta, ...metaOverrides }
1276
- };
1556
+ let updated = field;
1557
+ if (override) {
1558
+ const { options, label, ...metaOverrides } = override;
1559
+ updated = {
1560
+ ...field,
1561
+ ...label !== void 0 ? { label } : {},
1562
+ ...options !== void 0 ? { options } : {},
1563
+ meta: { ...field.meta, ...metaOverrides }
1564
+ };
1565
+ }
1566
+ if (updated.type === "array") {
1567
+ const prefix = `${updated.name}.`;
1568
+ let rowDynamicMeta;
1569
+ for (const [key, value] of Object.entries(overrides)) {
1570
+ if (!key.startsWith(prefix)) continue;
1571
+ const match = ROW_KEY_PATTERN.exec(key);
1572
+ if (!match) continue;
1573
+ const [, matchedArrayName, indexStr, childField] = match;
1574
+ if (matchedArrayName !== updated.name) continue;
1575
+ const rowIndex = Number(indexStr);
1576
+ if (!rowDynamicMeta) rowDynamicMeta = {};
1577
+ if (!rowDynamicMeta[rowIndex]) rowDynamicMeta[rowIndex] = {};
1578
+ rowDynamicMeta[rowIndex][childField] = value;
1579
+ }
1580
+ if (rowDynamicMeta) {
1581
+ updated = {
1582
+ ...updated,
1583
+ _rowDynamicMeta: rowDynamicMeta
1584
+ };
1585
+ }
1586
+ }
1587
+ return updated;
1277
1588
  });
1278
1589
  }
1279
1590
  function buildDefaults(fields) {
@@ -1303,6 +1614,12 @@ function buildDefaults(fields) {
1303
1614
  }
1304
1615
  return result;
1305
1616
  }
1617
+
1618
+ // src/utils/resolveNullableSlot.ts
1619
+ function resolveNullableSlot(slot, fallback) {
1620
+ if (slot === null) return null;
1621
+ return slot ?? fallback;
1622
+ }
1306
1623
  function AutoForm(props) {
1307
1624
  const {
1308
1625
  form: uniForm,
@@ -1527,24 +1844,33 @@ function AutoForm(props) {
1527
1844
  const visibleFields = useConditionalFields(fieldsWithDynamic, control);
1528
1845
  const sections = useSectionGrouping(visibleFields);
1529
1846
  const resolvedLayout = React3__namespace.useMemo(() => {
1530
- const base = layout?.arrayButtons?.base ?? DefaultArrayButton;
1847
+ const base = resolveNullableSlot(
1848
+ layout?.arrayButtons?.base,
1849
+ DefaultArrayButton
1850
+ );
1531
1851
  const slots = layout?.arrayButtons;
1532
1852
  return {
1533
1853
  formWrapper: layout?.formWrapper ?? DefaultFormWrapper,
1534
1854
  sectionWrapper: layout?.sectionWrapper ?? DefaultSectionWrapper,
1535
- submitButton: layout?.submitButton ?? DefaultSubmitButton,
1855
+ submitButton: resolveNullableSlot(
1856
+ layout?.submitButton,
1857
+ DefaultSubmitButton
1858
+ ),
1536
1859
  arrayRowLayout: layout?.arrayRowLayout ?? DefaultArrayRowLayout,
1537
1860
  arrayFieldLayout: layout?.arrayFieldLayout ?? DefaultArrayFieldLayout,
1538
1861
  objectWrapper: layout?.objectWrapper ?? DefaultObjectWrapper,
1539
1862
  arrayWrapper: layout?.arrayWrapper ?? DefaultArrayWrapper,
1540
1863
  arrayButtons: {
1541
1864
  base,
1542
- add: slots?.add ?? base,
1543
- remove: slots?.remove ?? base,
1544
- moveUp: slots?.moveUp ?? base,
1545
- moveDown: slots?.moveDown ?? base,
1546
- duplicate: slots?.duplicate ?? base,
1547
- collapse: slots?.collapse ?? DefaultArrayCollapseButton
1865
+ add: resolveNullableSlot(slots?.add, base),
1866
+ remove: resolveNullableSlot(slots?.remove, base),
1867
+ moveUp: resolveNullableSlot(slots?.moveUp, base),
1868
+ moveDown: resolveNullableSlot(slots?.moveDown, base),
1869
+ duplicate: resolveNullableSlot(slots?.duplicate, base),
1870
+ collapse: resolveNullableSlot(
1871
+ slots?.collapse,
1872
+ DefaultArrayCollapseButton
1873
+ )
1548
1874
  },
1549
1875
  loadingFallback: layout?.loadingFallback ?? /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Loading\u2026" })
1550
1876
  };
@@ -1566,6 +1892,7 @@ function AutoForm(props) {
1566
1892
  const contextValue = React3__namespace.useMemo(
1567
1893
  () => ({
1568
1894
  registry,
1895
+ fieldConfigs: mergedFields,
1569
1896
  fieldOverrides: fieldOverridesProp,
1570
1897
  fieldWrapper: resolvedFieldWrapper,
1571
1898
  layout: resolvedLayout,
@@ -1574,10 +1901,13 @@ function AutoForm(props) {
1574
1901
  coercions,
1575
1902
  messages,
1576
1903
  labels,
1577
- formMethods
1904
+ formMethods,
1905
+ control,
1906
+ setDynamicMeta
1578
1907
  }),
1579
1908
  [
1580
1909
  registry,
1910
+ mergedFields,
1581
1911
  fieldOverridesProp,
1582
1912
  resolvedFieldWrapper,
1583
1913
  resolvedLayout,
@@ -1586,7 +1916,9 @@ function AutoForm(props) {
1586
1916
  coercions,
1587
1917
  messages,
1588
1918
  labels,
1589
- formMethods
1919
+ formMethods,
1920
+ control,
1921
+ setDynamicMeta
1590
1922
  ]
1591
1923
  );
1592
1924
  if (isLoadingDefaults) {
@@ -1630,13 +1962,13 @@ function AutoForm(props) {
1630
1962
  section.title
1631
1963
  );
1632
1964
  }),
1633
- /* @__PURE__ */ jsxRuntime.jsx(
1965
+ SubmitButton ? /* @__PURE__ */ jsxRuntime.jsx(
1634
1966
  SubmitButton,
1635
1967
  {
1636
1968
  isSubmitting: formState.isSubmitting,
1637
1969
  label: labels.submit ?? "Submit"
1638
1970
  }
1639
- )
1971
+ ) : null
1640
1972
  ] })
1641
1973
  }
1642
1974
  ) });
@@ -1703,6 +2035,10 @@ var UniForm = class {
1703
2035
  * Replaces any previously registered handler for that field — only one
1704
2036
  * handler per field is kept. This prevents accidental handler accumulation
1705
2037
  * when called inside a React render cycle.
2038
+ *
2039
+ * Supports both generic array paths (`"tasks.priority"` — fires for all rows)
2040
+ * and indexed paths (`"tasks.0.priority"` — fires only for row 0).
2041
+ *
1706
2042
  * Returns `this` for fluent chaining.
1707
2043
  */
1708
2044
  setOnChange(field, handler) {
@@ -1735,6 +2071,34 @@ var UniForm = class {
1735
2071
  function createForm(schema) {
1736
2072
  return new UniForm(schema);
1737
2073
  }
2074
+ function findArrayConfig(fields, name) {
2075
+ for (const field of fields) {
2076
+ if (field.name === name) {
2077
+ return field.type === "array" ? field : void 0;
2078
+ }
2079
+ if (field.type === "object") {
2080
+ const found = findArrayConfig(field.children, name);
2081
+ if (found) return found;
2082
+ }
2083
+ }
2084
+ return void 0;
2085
+ }
2086
+ function useArrayField(fieldName) {
2087
+ const { control, fieldConfigs } = useAutoFormContext();
2088
+ const result = reactHookForm.useFieldArray({ control, name: fieldName });
2089
+ const rowCount = result.fields.length;
2090
+ const config = findArrayConfig(fieldConfigs, fieldName);
2091
+ const minItems = config?.minItems;
2092
+ const maxItems = config?.maxItems;
2093
+ const canAdd = maxItems == null || rowCount < maxItems;
2094
+ const atMin = minItems != null && rowCount <= minItems;
2095
+ return {
2096
+ ...result,
2097
+ rowCount,
2098
+ canAdd,
2099
+ atMin
2100
+ };
2101
+ }
1738
2102
 
1739
2103
  exports.AutoForm = AutoForm;
1740
2104
  exports.DefaultArrayButton = DefaultArrayButton;
@@ -1758,6 +2122,7 @@ exports.defaultRegistry = defaultRegistry;
1758
2122
  exports.introspectObjectSchema = introspectObjectSchema;
1759
2123
  exports.introspectSchema = introspectSchema;
1760
2124
  exports.mergeRegistries = mergeRegistries;
2125
+ exports.useArrayField = useArrayField;
1761
2126
  exports.useAutoFormContext = useAutoFormContext;
1762
2127
  exports.useConditionalFields = useConditionalFields;
1763
2128
  exports.useFormPersistence = useFormPersistence;