@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.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as z from 'zod/v4/core';
2
2
  import * as React3 from 'react';
3
3
  import { useMemo, useRef, useEffect, useCallback, useState } from 'react';
4
- import { useFormState, useWatch, useForm, useFieldArray, Controller } from 'react-hook-form';
4
+ import { useWatch, useFormState, useForm, useFieldArray, Controller } from 'react-hook-form';
5
5
  import { zodResolver } from '@hookform/resolvers/zod';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
7
 
@@ -635,6 +635,24 @@ function SelectField({
635
635
  }
636
636
  );
637
637
  }
638
+ function useConditionalFields(fields, control, scopeName) {
639
+ const allValues = useWatch({ control });
640
+ const scopedValues = useWatch({ control, name: scopeName });
641
+ const values = scopeName ? scopedValues : allValues;
642
+ return useMemo(() => {
643
+ return fields.filter((field) => {
644
+ if (field.meta.hidden) return false;
645
+ if (typeof field.meta.condition === "function") {
646
+ return field.meta.condition(values);
647
+ }
648
+ return true;
649
+ }).sort((a, b) => {
650
+ const orderA = typeof a.meta.order === "number" ? a.meta.order : Infinity;
651
+ const orderB = typeof b.meta.order === "number" ? b.meta.order : Infinity;
652
+ return orderA - orderB;
653
+ });
654
+ }, [fields, values]);
655
+ }
638
656
  function ObjectField({
639
657
  field,
640
658
  control,
@@ -643,7 +661,7 @@ function ObjectField({
643
661
  shouldUnregister
644
662
  }) {
645
663
  const { classNames, layout } = useAutoFormContext();
646
- const children = field.children;
664
+ const children = useConditionalFields(field.children, control, namePrefix);
647
665
  const content = children.map((child, idx) => /* @__PURE__ */ jsx(
648
666
  FieldRenderer,
649
667
  {
@@ -698,6 +716,124 @@ function getDefaultValue(field) {
698
716
  return void 0;
699
717
  }
700
718
  }
719
+
720
+ // src/utils/reindexDynamicMeta.ts
721
+ function buildKeyPattern(arrayName) {
722
+ return new RegExp(`^${escapeRegExp(arrayName)}\\.(\\d+)\\.(.+)$`);
723
+ }
724
+ function escapeRegExp(str) {
725
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
726
+ }
727
+ function reindexDynamicMeta(dynamicMeta, arrayName, mutation) {
728
+ const pattern = buildKeyPattern(arrayName);
729
+ const matched = [];
730
+ const result = {};
731
+ for (const [key, value] of Object.entries(dynamicMeta)) {
732
+ const match = pattern.exec(key);
733
+ if (match) {
734
+ matched.push({
735
+ key,
736
+ index: parseInt(match[1], 10),
737
+ childField: match[2],
738
+ value
739
+ });
740
+ } else {
741
+ result[key] = value;
742
+ }
743
+ }
744
+ if (!isValidMutation(mutation)) {
745
+ return dynamicMeta;
746
+ }
747
+ switch (mutation.type) {
748
+ case "remove":
749
+ applyRemove(matched, mutation.index, arrayName, result);
750
+ break;
751
+ case "move":
752
+ applyMove(matched, mutation.from, mutation.to, arrayName, result);
753
+ break;
754
+ case "duplicate":
755
+ applyDuplicate(matched, mutation.index, arrayName, result);
756
+ break;
757
+ case "add":
758
+ applyAdd(matched, mutation.index, arrayName, result);
759
+ break;
760
+ }
761
+ return result;
762
+ }
763
+ function isValidMutation(mutation, _entries) {
764
+ switch (mutation.type) {
765
+ case "remove":
766
+ return mutation.index >= 0;
767
+ case "move":
768
+ return mutation.from >= 0 && mutation.to >= 0;
769
+ case "duplicate":
770
+ return mutation.index >= 0;
771
+ case "add":
772
+ return mutation.index >= 0;
773
+ }
774
+ }
775
+ function buildKey(arrayName, index, childField) {
776
+ return `${arrayName}.${index}.${childField}`;
777
+ }
778
+ function applyRemove(entries, removedIndex, arrayName, result) {
779
+ for (const entry of entries) {
780
+ if (entry.index === removedIndex) {
781
+ continue;
782
+ } else if (entry.index > removedIndex) {
783
+ result[buildKey(arrayName, entry.index - 1, entry.childField)] = entry.value;
784
+ } else {
785
+ result[buildKey(arrayName, entry.index, entry.childField)] = entry.value;
786
+ }
787
+ }
788
+ }
789
+ function applyMove(entries, from, to, arrayName, result) {
790
+ if (from === to) {
791
+ for (const entry of entries) {
792
+ result[buildKey(arrayName, entry.index, entry.childField)] = entry.value;
793
+ }
794
+ return;
795
+ }
796
+ for (const entry of entries) {
797
+ let newIndex;
798
+ if (entry.index === from) {
799
+ newIndex = to;
800
+ } else if (from < to) {
801
+ if (entry.index > from && entry.index <= to) {
802
+ newIndex = entry.index - 1;
803
+ } else {
804
+ newIndex = entry.index;
805
+ }
806
+ } else {
807
+ if (entry.index >= to && entry.index < from) {
808
+ newIndex = entry.index + 1;
809
+ } else {
810
+ newIndex = entry.index;
811
+ }
812
+ }
813
+ result[buildKey(arrayName, newIndex, entry.childField)] = entry.value;
814
+ }
815
+ }
816
+ function applyDuplicate(entries, sourceIndex, arrayName, result) {
817
+ for (const entry of entries) {
818
+ if (entry.index === sourceIndex) {
819
+ result[buildKey(arrayName, entry.index, entry.childField)] = entry.value;
820
+ result[buildKey(arrayName, sourceIndex + 1, entry.childField)] = entry.value;
821
+ } else if (entry.index > sourceIndex) {
822
+ result[buildKey(arrayName, entry.index + 1, entry.childField)] = entry.value;
823
+ } else {
824
+ result[buildKey(arrayName, entry.index, entry.childField)] = entry.value;
825
+ }
826
+ }
827
+ }
828
+ function applyAdd(entries, newIndex, arrayName, result) {
829
+ for (const entry of entries) {
830
+ if (entry.index >= newIndex) {
831
+ result[buildKey(arrayName, entry.index + 1, entry.childField)] = entry.value;
832
+ } else {
833
+ result[buildKey(arrayName, entry.index, entry.childField)] = entry.value;
834
+ }
835
+ }
836
+ }
701
837
  function getRowSummary(row, itemConfig, index, itemSummary) {
702
838
  if (itemConfig.type === "object") {
703
839
  for (const child of itemConfig.children) {
@@ -710,8 +846,43 @@ function getRowSummary(row, itemConfig, index, itemSummary) {
710
846
  }
711
847
  return itemSummary?.(index) ?? `Item ${index + 1}`;
712
848
  }
849
+ function bindRowIndexToItemConfig(itemConfig, rowIndex) {
850
+ if (itemConfig.type !== "object") return itemConfig;
851
+ const children = itemConfig.children.map((child) => {
852
+ if (!child.meta.onChange) return child;
853
+ const originalOnChange = child.meta.onChange;
854
+ return {
855
+ ...child,
856
+ meta: {
857
+ ...child.meta,
858
+ onChange: (value, formMethods) => {
859
+ void originalOnChange(value, formMethods, rowIndex);
860
+ }
861
+ }
862
+ };
863
+ });
864
+ return { ...itemConfig, children };
865
+ }
866
+ function applyRowDynamicMeta(itemConfig, rowOverrides) {
867
+ if (itemConfig.type !== "object") return itemConfig;
868
+ const children = itemConfig.children.map((child) => {
869
+ const override = rowOverrides[child.name];
870
+ if (!override) return child;
871
+ const { options, label, ...metaOverrides } = override;
872
+ let updated = {
873
+ ...child,
874
+ ...label !== void 0 ? { label } : {},
875
+ meta: { ...child.meta, ...metaOverrides }
876
+ };
877
+ if (options !== void 0 && updated.type === "select") {
878
+ updated = { ...updated, options };
879
+ }
880
+ return updated;
881
+ });
882
+ return { ...itemConfig, children };
883
+ }
713
884
  function ArrayField({ field, control, effectiveName }) {
714
- const { classNames, layout, labels } = useAutoFormContext();
885
+ const { classNames, layout, labels, setDynamicMeta } = useAutoFormContext();
715
886
  const {
716
887
  fields: rows,
717
888
  append,
@@ -757,9 +928,10 @@ function ArrayField({ field, control, effectiveName }) {
757
928
  } = layout.arrayButtons;
758
929
  const ArrayFieldLayout = layout.arrayFieldLayout;
759
930
  const RowLayout = layout.arrayRowLayout;
931
+ const rowDynamicMeta = field._rowDynamicMeta;
760
932
  const renderedRows = rows.map((row, index) => {
761
933
  const isCollapsed = showCollapse && collapsed.has(index);
762
- const collapseButton = showCollapse ? /* @__PURE__ */ jsxs(
934
+ const collapseButton = showCollapse && CollapseBtn ? /* @__PURE__ */ jsxs(
763
935
  CollapseBtn,
764
936
  {
765
937
  type: "button",
@@ -783,29 +955,47 @@ function ArrayField({ field, control, effectiveName }) {
783
955
  ]
784
956
  }
785
957
  ) : null;
786
- const moveUpButton = showMove && rows.length > 1 ? /* @__PURE__ */ jsx(
958
+ const moveUpButton = showMove && rows.length > 1 && MoveUpBtn ? /* @__PURE__ */ jsx(
787
959
  MoveUpBtn,
788
960
  {
789
961
  type: "button",
790
962
  className: classNames.arrayMove,
791
- onClick: () => move(index, index - 1),
963
+ onClick: () => {
964
+ move(index, index - 1);
965
+ setDynamicMeta(
966
+ (prev) => reindexDynamicMeta(prev, effectiveName, {
967
+ type: "move",
968
+ from: index,
969
+ to: index - 1
970
+ })
971
+ );
972
+ },
792
973
  disabled: index === 0,
793
974
  "aria-label": labels.arrayAriaMoveUp?.(index) ?? `Move item ${index + 1} up`,
794
975
  children: labels.arrayMoveUp ?? "\u2191"
795
976
  }
796
977
  ) : null;
797
- const moveDownButton = showMove && rows.length > 1 ? /* @__PURE__ */ jsx(
978
+ const moveDownButton = showMove && rows.length > 1 && MoveDownBtn ? /* @__PURE__ */ jsx(
798
979
  MoveDownBtn,
799
980
  {
800
981
  type: "button",
801
982
  className: classNames.arrayMove,
802
- onClick: () => move(index, index + 1),
983
+ onClick: () => {
984
+ move(index, index + 1);
985
+ setDynamicMeta(
986
+ (prev) => reindexDynamicMeta(prev, effectiveName, {
987
+ type: "move",
988
+ from: index,
989
+ to: index + 1
990
+ })
991
+ );
992
+ },
803
993
  disabled: index === rows.length - 1,
804
994
  "aria-label": labels.arrayAriaMoveDown?.(index) ?? `Move item ${index + 1} down`,
805
995
  children: labels.arrayMoveDown ?? "\u2193"
806
996
  }
807
997
  ) : null;
808
- const duplicateButton = showDuplicate && !atMax ? /* @__PURE__ */ jsx(
998
+ const duplicateButton = showDuplicate && !atMax && DuplicateBtn ? /* @__PURE__ */ jsx(
809
999
  DuplicateBtn,
810
1000
  {
811
1001
  type: "button",
@@ -815,26 +1005,43 @@ function ArrayField({ field, control, effectiveName }) {
815
1005
  Object.entries(row).filter(([k]) => k !== "id")
816
1006
  );
817
1007
  insert(index + 1, values);
1008
+ setDynamicMeta(
1009
+ (prev) => reindexDynamicMeta(prev, effectiveName, {
1010
+ type: "duplicate",
1011
+ index
1012
+ })
1013
+ );
818
1014
  },
819
1015
  "aria-label": labels.arrayAriaDuplicate?.(index) ?? `Duplicate item ${index + 1}`,
820
1016
  children: labels.arrayDuplicate ?? "Duplicate"
821
1017
  }
822
1018
  ) : null;
823
- const removeButton = /* @__PURE__ */ jsx(
1019
+ const removeButton = RemoveBtn ? /* @__PURE__ */ jsx(
824
1020
  RemoveBtn,
825
1021
  {
826
1022
  type: "button",
827
1023
  className: classNames.arrayRemove,
828
- onClick: () => remove(index),
1024
+ onClick: () => {
1025
+ remove(index);
1026
+ setDynamicMeta(
1027
+ (prev) => reindexDynamicMeta(prev, effectiveName, {
1028
+ type: "remove",
1029
+ index
1030
+ })
1031
+ );
1032
+ },
829
1033
  disabled: atMin,
830
1034
  "aria-label": labels.arrayAriaRemove?.(index) ?? `Remove item ${index + 1}`,
831
1035
  children: labels.arrayRemove ?? "Remove"
832
1036
  }
833
- );
1037
+ ) : null;
834
1038
  const fieldContent = !isCollapsed ? /* @__PURE__ */ jsx(
835
1039
  FieldRenderer,
836
1040
  {
837
- field: effectiveItemConfig,
1041
+ field: bindRowIndexToItemConfig(
1042
+ rowDynamicMeta?.[index] ? applyRowDynamicMeta(effectiveItemConfig, rowDynamicMeta[index]) : effectiveItemConfig,
1043
+ index
1044
+ ),
838
1045
  control,
839
1046
  namePrefix: `${effectiveName}.${index}`
840
1047
  }
@@ -856,16 +1063,25 @@ function ArrayField({ field, control, effectiveName }) {
856
1063
  row.id
857
1064
  );
858
1065
  });
859
- const addButton = /* @__PURE__ */ jsx(
1066
+ const addButton = AddBtn ? /* @__PURE__ */ jsx(
860
1067
  AddBtn,
861
1068
  {
862
1069
  type: "button",
863
1070
  className: classNames.arrayAdd,
864
1071
  disabled: atMax,
865
- onClick: () => append(getDefaultValue(itemConfig)),
1072
+ onClick: () => {
1073
+ const newIndex = rows.length;
1074
+ append(getDefaultValue(itemConfig));
1075
+ setDynamicMeta(
1076
+ (prev) => reindexDynamicMeta(prev, effectiveName, {
1077
+ type: "add",
1078
+ index: newIndex
1079
+ })
1080
+ );
1081
+ },
866
1082
  children: labels.arrayAdd ?? "Add"
867
1083
  }
868
- );
1084
+ ) : null;
869
1085
  const content = /* @__PURE__ */ jsx(
870
1086
  ArrayFieldLayout,
871
1087
  {
@@ -1027,22 +1243,6 @@ function FieldRenderer({
1027
1243
  }
1028
1244
  );
1029
1245
  }
1030
- function useConditionalFields(fields, control) {
1031
- const values = useWatch({ control });
1032
- return useMemo(() => {
1033
- return fields.filter((field) => {
1034
- if (field.meta.hidden) return false;
1035
- if (typeof field.meta.condition === "function") {
1036
- return field.meta.condition(values);
1037
- }
1038
- return true;
1039
- }).sort((a, b) => {
1040
- const orderA = typeof a.meta.order === "number" ? a.meta.order : Infinity;
1041
- const orderB = typeof b.meta.order === "number" ? b.meta.order : Infinity;
1042
- return orderA - orderB;
1043
- });
1044
- }, [fields, values]);
1045
- }
1046
1246
  function useSectionGrouping(fields) {
1047
1247
  return useMemo(() => {
1048
1248
  const ungrouped = [];
@@ -1131,6 +1331,28 @@ function useLatestRef(value) {
1131
1331
  return ref;
1132
1332
  }
1133
1333
 
1334
+ // src/utils/createRowScopedContext.ts
1335
+ function createRowScopedContext(baseCtx, arrayName, rowIndex, itemFieldNames, getValues) {
1336
+ return {
1337
+ ...baseCtx,
1338
+ getValues,
1339
+ setFieldMeta: (field, meta) => {
1340
+ const fieldStr = field;
1341
+ const prefix = arrayName + ".";
1342
+ const childName = fieldStr.startsWith(prefix) ? fieldStr.slice(prefix.length) : fieldStr;
1343
+ if (itemFieldNames.has(childName)) {
1344
+ const qualifiedKey = `${arrayName}.${rowIndex}.${childName}`;
1345
+ baseCtx.setFieldMeta(
1346
+ qualifiedKey,
1347
+ meta
1348
+ );
1349
+ } else {
1350
+ baseCtx.setFieldMeta(field, meta);
1351
+ }
1352
+ }
1353
+ };
1354
+ }
1355
+
1134
1356
  // src/utils/fieldPipeline.ts
1135
1357
  function applyFieldOverrides(fields, overrides) {
1136
1358
  return fields.map((field) => {
@@ -1191,10 +1413,74 @@ function injectOnChangeHandlers(fields, uniForm, ctx, handlerKeys = new Set(uniF
1191
1413
  } else if (updated.type === "array") {
1192
1414
  const prefix = field.name + ".";
1193
1415
  const itemKeys = /* @__PURE__ */ new Set();
1416
+ const indexedKeys = /* @__PURE__ */ new Set();
1194
1417
  for (const key of handlerKeys) {
1195
- if (key.startsWith(prefix)) itemKeys.add(key.slice(prefix.length));
1418
+ if (key.startsWith(prefix)) {
1419
+ const remainder = key.slice(prefix.length);
1420
+ const indexMatch = remainder.match(/^(\d+)\.(.+)$/);
1421
+ if (indexMatch) {
1422
+ indexedKeys.add(remainder);
1423
+ itemKeys.add(indexMatch[2]);
1424
+ } else {
1425
+ itemKeys.add(remainder);
1426
+ }
1427
+ }
1196
1428
  }
1197
- if (itemKeys.size) {
1429
+ if (itemKeys.size && updated.itemConfig.type === "object") {
1430
+ const itemFieldNames = new Set(
1431
+ updated.itemConfig.children.map((c) => c.name)
1432
+ );
1433
+ const arrayName = field.name;
1434
+ const newChildren = updated.itemConfig.children.map((child) => {
1435
+ if (!itemKeys.has(child.name)) return child;
1436
+ const existingOnChange = child.meta.onChange;
1437
+ const rowAwareHandler = (value, formMethods, rowIndex) => {
1438
+ void existingOnChange?.(value, formMethods);
1439
+ const rowCtx = createRowScopedContext(
1440
+ ctx,
1441
+ arrayName,
1442
+ rowIndex,
1443
+ itemFieldNames,
1444
+ () => {
1445
+ const allValues = ctx.getValues();
1446
+ const arrayValues = allValues?.[arrayName];
1447
+ if (Array.isArray(arrayValues)) {
1448
+ return arrayValues[rowIndex] ?? {};
1449
+ }
1450
+ return {};
1451
+ }
1452
+ );
1453
+ void uniForm._fireHandler(
1454
+ `${arrayName}.${child.name}`,
1455
+ value,
1456
+ rowCtx
1457
+ );
1458
+ const indexedKey = `${rowIndex}.${child.name}`;
1459
+ if (indexedKeys.has(indexedKey)) {
1460
+ void uniForm._fireHandler(
1461
+ `${arrayName}.${indexedKey}`,
1462
+ value,
1463
+ rowCtx
1464
+ );
1465
+ }
1466
+ };
1467
+ return {
1468
+ ...child,
1469
+ meta: {
1470
+ ...child.meta,
1471
+ // Cast to FieldMeta onChange type — ArrayField's bindRowIndexToItemConfig
1472
+ // will call this with the rowIndex third argument at render time.
1473
+ onChange: rowAwareHandler
1474
+ }
1475
+ };
1476
+ });
1477
+ const newItemConfig = {
1478
+ ...updated.itemConfig,
1479
+ children: newChildren
1480
+ };
1481
+ if (newItemConfig !== updated.itemConfig)
1482
+ updated = { ...updated, itemConfig: newItemConfig };
1483
+ } else if (itemKeys.size) {
1198
1484
  const remappedUniForm = {
1199
1485
  _getWatchedFields: () => Array.from(itemKeys),
1200
1486
  _fireHandlers: (name, value, c) => uniForm._fireHandler(`${field.name}.${name}`, value, c)
@@ -1240,18 +1526,43 @@ function injectConditions(fields, conditions) {
1240
1526
  return updated;
1241
1527
  });
1242
1528
  }
1529
+ var ROW_KEY_PATTERN = /^(.+?)\.(\d+)\.(.+)$/;
1243
1530
  function applyDynamicMeta(fields, overrides) {
1244
1531
  if (!Object.keys(overrides).length) return fields;
1245
1532
  return fields.map((field) => {
1246
1533
  const override = overrides[field.name];
1247
- if (!override) return field;
1248
- const { options, label, ...metaOverrides } = override;
1249
- return {
1250
- ...field,
1251
- ...label !== void 0 ? { label } : {},
1252
- ...options !== void 0 ? { options } : {},
1253
- meta: { ...field.meta, ...metaOverrides }
1254
- };
1534
+ let updated = field;
1535
+ if (override) {
1536
+ const { options, label, ...metaOverrides } = override;
1537
+ updated = {
1538
+ ...field,
1539
+ ...label !== void 0 ? { label } : {},
1540
+ ...options !== void 0 ? { options } : {},
1541
+ meta: { ...field.meta, ...metaOverrides }
1542
+ };
1543
+ }
1544
+ if (updated.type === "array") {
1545
+ const prefix = `${updated.name}.`;
1546
+ let rowDynamicMeta;
1547
+ for (const [key, value] of Object.entries(overrides)) {
1548
+ if (!key.startsWith(prefix)) continue;
1549
+ const match = ROW_KEY_PATTERN.exec(key);
1550
+ if (!match) continue;
1551
+ const [, matchedArrayName, indexStr, childField] = match;
1552
+ if (matchedArrayName !== updated.name) continue;
1553
+ const rowIndex = Number(indexStr);
1554
+ if (!rowDynamicMeta) rowDynamicMeta = {};
1555
+ if (!rowDynamicMeta[rowIndex]) rowDynamicMeta[rowIndex] = {};
1556
+ rowDynamicMeta[rowIndex][childField] = value;
1557
+ }
1558
+ if (rowDynamicMeta) {
1559
+ updated = {
1560
+ ...updated,
1561
+ _rowDynamicMeta: rowDynamicMeta
1562
+ };
1563
+ }
1564
+ }
1565
+ return updated;
1255
1566
  });
1256
1567
  }
1257
1568
  function buildDefaults(fields) {
@@ -1281,6 +1592,12 @@ function buildDefaults(fields) {
1281
1592
  }
1282
1593
  return result;
1283
1594
  }
1595
+
1596
+ // src/utils/resolveNullableSlot.ts
1597
+ function resolveNullableSlot(slot, fallback) {
1598
+ if (slot === null) return null;
1599
+ return slot ?? fallback;
1600
+ }
1284
1601
  function AutoForm(props) {
1285
1602
  const {
1286
1603
  form: uniForm,
@@ -1505,24 +1822,33 @@ function AutoForm(props) {
1505
1822
  const visibleFields = useConditionalFields(fieldsWithDynamic, control);
1506
1823
  const sections = useSectionGrouping(visibleFields);
1507
1824
  const resolvedLayout = React3.useMemo(() => {
1508
- const base = layout?.arrayButtons?.base ?? DefaultArrayButton;
1825
+ const base = resolveNullableSlot(
1826
+ layout?.arrayButtons?.base,
1827
+ DefaultArrayButton
1828
+ );
1509
1829
  const slots = layout?.arrayButtons;
1510
1830
  return {
1511
1831
  formWrapper: layout?.formWrapper ?? DefaultFormWrapper,
1512
1832
  sectionWrapper: layout?.sectionWrapper ?? DefaultSectionWrapper,
1513
- submitButton: layout?.submitButton ?? DefaultSubmitButton,
1833
+ submitButton: resolveNullableSlot(
1834
+ layout?.submitButton,
1835
+ DefaultSubmitButton
1836
+ ),
1514
1837
  arrayRowLayout: layout?.arrayRowLayout ?? DefaultArrayRowLayout,
1515
1838
  arrayFieldLayout: layout?.arrayFieldLayout ?? DefaultArrayFieldLayout,
1516
1839
  objectWrapper: layout?.objectWrapper ?? DefaultObjectWrapper,
1517
1840
  arrayWrapper: layout?.arrayWrapper ?? DefaultArrayWrapper,
1518
1841
  arrayButtons: {
1519
1842
  base,
1520
- add: slots?.add ?? base,
1521
- remove: slots?.remove ?? base,
1522
- moveUp: slots?.moveUp ?? base,
1523
- moveDown: slots?.moveDown ?? base,
1524
- duplicate: slots?.duplicate ?? base,
1525
- collapse: slots?.collapse ?? DefaultArrayCollapseButton
1843
+ add: resolveNullableSlot(slots?.add, base),
1844
+ remove: resolveNullableSlot(slots?.remove, base),
1845
+ moveUp: resolveNullableSlot(slots?.moveUp, base),
1846
+ moveDown: resolveNullableSlot(slots?.moveDown, base),
1847
+ duplicate: resolveNullableSlot(slots?.duplicate, base),
1848
+ collapse: resolveNullableSlot(
1849
+ slots?.collapse,
1850
+ DefaultArrayCollapseButton
1851
+ )
1526
1852
  },
1527
1853
  loadingFallback: layout?.loadingFallback ?? /* @__PURE__ */ jsx("p", { children: "Loading\u2026" })
1528
1854
  };
@@ -1544,6 +1870,7 @@ function AutoForm(props) {
1544
1870
  const contextValue = React3.useMemo(
1545
1871
  () => ({
1546
1872
  registry,
1873
+ fieldConfigs: mergedFields,
1547
1874
  fieldOverrides: fieldOverridesProp,
1548
1875
  fieldWrapper: resolvedFieldWrapper,
1549
1876
  layout: resolvedLayout,
@@ -1552,10 +1879,13 @@ function AutoForm(props) {
1552
1879
  coercions,
1553
1880
  messages,
1554
1881
  labels,
1555
- formMethods
1882
+ formMethods,
1883
+ control,
1884
+ setDynamicMeta
1556
1885
  }),
1557
1886
  [
1558
1887
  registry,
1888
+ mergedFields,
1559
1889
  fieldOverridesProp,
1560
1890
  resolvedFieldWrapper,
1561
1891
  resolvedLayout,
@@ -1564,7 +1894,9 @@ function AutoForm(props) {
1564
1894
  coercions,
1565
1895
  messages,
1566
1896
  labels,
1567
- formMethods
1897
+ formMethods,
1898
+ control,
1899
+ setDynamicMeta
1568
1900
  ]
1569
1901
  );
1570
1902
  if (isLoadingDefaults) {
@@ -1608,13 +1940,13 @@ function AutoForm(props) {
1608
1940
  section.title
1609
1941
  );
1610
1942
  }),
1611
- /* @__PURE__ */ jsx(
1943
+ SubmitButton ? /* @__PURE__ */ jsx(
1612
1944
  SubmitButton,
1613
1945
  {
1614
1946
  isSubmitting: formState.isSubmitting,
1615
1947
  label: labels.submit ?? "Submit"
1616
1948
  }
1617
- )
1949
+ ) : null
1618
1950
  ] })
1619
1951
  }
1620
1952
  ) });
@@ -1681,6 +2013,10 @@ var UniForm = class {
1681
2013
  * Replaces any previously registered handler for that field — only one
1682
2014
  * handler per field is kept. This prevents accidental handler accumulation
1683
2015
  * when called inside a React render cycle.
2016
+ *
2017
+ * Supports both generic array paths (`"tasks.priority"` — fires for all rows)
2018
+ * and indexed paths (`"tasks.0.priority"` — fires only for row 0).
2019
+ *
1684
2020
  * Returns `this` for fluent chaining.
1685
2021
  */
1686
2022
  setOnChange(field, handler) {
@@ -1713,7 +2049,35 @@ var UniForm = class {
1713
2049
  function createForm(schema) {
1714
2050
  return new UniForm(schema);
1715
2051
  }
2052
+ function findArrayConfig(fields, name) {
2053
+ for (const field of fields) {
2054
+ if (field.name === name) {
2055
+ return field.type === "array" ? field : void 0;
2056
+ }
2057
+ if (field.type === "object") {
2058
+ const found = findArrayConfig(field.children, name);
2059
+ if (found) return found;
2060
+ }
2061
+ }
2062
+ return void 0;
2063
+ }
2064
+ function useArrayField(fieldName) {
2065
+ const { control, fieldConfigs } = useAutoFormContext();
2066
+ const result = useFieldArray({ control, name: fieldName });
2067
+ const rowCount = result.fields.length;
2068
+ const config = findArrayConfig(fieldConfigs, fieldName);
2069
+ const minItems = config?.minItems;
2070
+ const maxItems = config?.maxItems;
2071
+ const canAdd = maxItems == null || rowCount < maxItems;
2072
+ const atMin = minItems != null && rowCount <= minItems;
2073
+ return {
2074
+ ...result,
2075
+ rowCount,
2076
+ canAdd,
2077
+ atMin
2078
+ };
2079
+ }
1716
2080
 
1717
- export { AutoForm, DefaultArrayButton, DefaultArrayCollapseButton, DefaultArrayFieldLayout, DefaultArrayRowLayout, DefaultArrayWrapper, DefaultCheckbox, DefaultFieldWrapper, DefaultInput, DefaultObjectWrapper, DefaultSelect, DefaultSubmitButton, FieldRenderer, UniForm, coerceValue, createAutoForm, createForm, defaultCoercionMap, defaultRegistry, introspectObjectSchema, introspectSchema, mergeRegistries, useAutoFormContext, useConditionalFields, useFormPersistence, useSectionGrouping };
2081
+ export { AutoForm, DefaultArrayButton, DefaultArrayCollapseButton, DefaultArrayFieldLayout, DefaultArrayRowLayout, DefaultArrayWrapper, DefaultCheckbox, DefaultFieldWrapper, DefaultInput, DefaultObjectWrapper, DefaultSelect, DefaultSubmitButton, FieldRenderer, UniForm, coerceValue, createAutoForm, createForm, defaultCoercionMap, defaultRegistry, introspectObjectSchema, introspectSchema, mergeRegistries, useArrayField, useAutoFormContext, useConditionalFields, useFormPersistence, useSectionGrouping };
1718
2082
  //# sourceMappingURL=index.mjs.map
1719
2083
  //# sourceMappingURL=index.mjs.map