coles-solid-library 0.4.7 → 0.4.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.esm.js CHANGED
@@ -1493,13 +1493,329 @@ var css_248z$e = "@charset \"UTF-8\";\n.input-module_input__-LMoi {\n width: 10
1493
1493
  var styles$9 = {"input":"input-module_input__-LMoi","disabled":"input-module_disabled__40ZHw","inFormField":"input-module_inFormField__kk-PD","transparent":"input-module_transparent__aXcGX"};
1494
1494
  styleInject(css_248z$e);
1495
1495
 
1496
+ /**
1497
+ * A class that encapsulates an array of FormGroup instances along with optional array-level validation.
1498
+ *
1499
+ * @template T - An object type representing the shape of a single form group's value.
1500
+ */
1501
+ class FormArray {
1502
+ /**
1503
+ * Array-level validators that validate the entire array of form group values.
1504
+ * @private
1505
+ */
1506
+ internalArrayValidation;
1507
+ /**
1508
+ * The FormGroup instances managed by this array.
1509
+ * @private
1510
+ */
1511
+ groups;
1512
+ /**
1513
+ * Solid store setter for the groups array.
1514
+ * @private
1515
+ */
1516
+ setGroups;
1517
+ /**
1518
+ * Array-level errors (from array-level validators only).
1519
+ * Per-item errors live in each FormGroup.
1520
+ * @private
1521
+ */
1522
+ arrayErrors = [];
1523
+ /**
1524
+ * Snapshot of initial groups for reset.
1525
+ * @private
1526
+ */
1527
+ _initialGroups;
1528
+ /**
1529
+ * Creates an instance of FormArray.
1530
+ *
1531
+ * @param groups - An array of FormGroup instances representing the initial items.
1532
+ * @param arrayValidators - Optional array-level validators (e.g., min/max item count).
1533
+ */
1534
+ constructor(groups = [], arrayValidators = []) {
1535
+ const [storeGroups, setStoreGroups] = createStore([...groups]);
1536
+ this.groups = storeGroups;
1537
+ this.setGroups = setStoreGroups;
1538
+ this.internalArrayValidation = arrayValidators;
1539
+ this._initialGroups = [...groups];
1540
+ }
1541
+ /**
1542
+ * Returns the number of items in the array.
1543
+ */
1544
+ get length() {
1545
+ return this.groups.length;
1546
+ }
1547
+ /**
1548
+ * Returns true if any validator (array-level or within any group) exists matching the provided errKey.
1549
+ * If no errKey provided, returns true when any validator exists at all.
1550
+ */
1551
+ hasAnyValidator(errKey) {
1552
+ if (!errKey) {
1553
+ return this.internalArrayValidation.length > 0 || this.groups.some(g => g.hasAnyValidator());
1554
+ }
1555
+ const inArray = this.internalArrayValidation.some(v => v.errKey === errKey);
1556
+ if (inArray) return true;
1557
+ return this.groups.some(g => g.hasAnyValidator(errKey));
1558
+ }
1559
+ /**
1560
+ * Resets the array to its initial groups and clears array-level errors.
1561
+ * Each initial FormGroup is reset to its own initial state.
1562
+ */
1563
+ reset() {
1564
+ this._initialGroups.forEach(g => g.reset());
1565
+ this.setGroups(() => [...this._initialGroups]);
1566
+ this.arrayErrors = [];
1567
+ }
1568
+ /**
1569
+ * Gets the current values of all form groups in the array.
1570
+ *
1571
+ * @returns An array of cloned plain-object values from each FormGroup.
1572
+ */
1573
+ get() {
1574
+ return this.groups.map(g => g.get());
1575
+ }
1576
+ /**
1577
+ * Gets the value of a specific form group in the array.
1578
+ *
1579
+ * @param index - The index of the group.
1580
+ * @returns A cloned copy of the group's value, or undefined if out of bounds.
1581
+ */
1582
+ getAt(index) {
1583
+ if (index < 0 || index >= this.groups.length) {
1584
+ return undefined;
1585
+ }
1586
+ return this.groups[index].get();
1587
+ }
1588
+ /**
1589
+ * Gets the FormGroup instance at the specified index.
1590
+ *
1591
+ * @param index - The index of the group.
1592
+ * @returns The FormGroup instance, or undefined if out of bounds.
1593
+ */
1594
+ getGroup(index) {
1595
+ if (index < 0 || index >= this.groups.length) {
1596
+ return undefined;
1597
+ }
1598
+ return this.groups[index];
1599
+ }
1600
+ /**
1601
+ * Returns a shallow copy of all FormGroup instances.
1602
+ */
1603
+ getGroups() {
1604
+ return [...this.groups];
1605
+ }
1606
+ /**
1607
+ * Gets a property value from the FormGroup at the specified index.
1608
+ *
1609
+ * @param index - The index of the group.
1610
+ * @param key - The property key to retrieve.
1611
+ * @returns The property value, or undefined if not found.
1612
+ */
1613
+ getProperty(index, key) {
1614
+ const group = this.getGroup(index);
1615
+ return group ? group.get(key) : undefined;
1616
+ }
1617
+ /**
1618
+ * Sets the values of all groups. Updates existing groups in-place and
1619
+ * discards any excess groups. Does not create new groups for excess values.
1620
+ *
1621
+ * @param values - The new values for the groups.
1622
+ */
1623
+ set(values) {
1624
+ const len = Math.min(values.length, this.groups.length);
1625
+ for (let i = 0; i < len; i++) {
1626
+ const val = values[i];
1627
+ for (const key in val) {
1628
+ this.groups[i].set(key, val[key]);
1629
+ }
1630
+ }
1631
+ // If fewer values than groups, remove excess groups
1632
+ if (values.length < this.groups.length) {
1633
+ this.setGroups(old => old.slice(0, values.length));
1634
+ }
1635
+ }
1636
+ /**
1637
+ * Sets the value of a group at the specified index.
1638
+ *
1639
+ * @param index - The index of the group to update.
1640
+ * @param value - The new value for the group.
1641
+ */
1642
+ setAt(index, value) {
1643
+ const group = this.getGroup(index);
1644
+ if (!group) {
1645
+ console.error(`Index ${index} out of bounds.`);
1646
+ return;
1647
+ }
1648
+ for (const key in value) {
1649
+ group.set(key, value[key]);
1650
+ }
1651
+ }
1652
+ /**
1653
+ * Sets a property value on the FormGroup at the specified index.
1654
+ *
1655
+ * @param index - The index of the group.
1656
+ * @param key - The property key to set.
1657
+ * @param value - The new value for the property.
1658
+ */
1659
+ setProperty(index, key, value) {
1660
+ const group = this.getGroup(index);
1661
+ if (!group) {
1662
+ console.error(`Index ${index} out of bounds.`);
1663
+ return;
1664
+ }
1665
+ group.set(key, value);
1666
+ }
1667
+ /**
1668
+ * Removes a group at the specified index.
1669
+ *
1670
+ * @param index - The index of the group to remove.
1671
+ */
1672
+ remove(index) {
1673
+ this.setGroups(old => {
1674
+ const copy = [...old];
1675
+ copy.splice(index, 1);
1676
+ return copy;
1677
+ });
1678
+ }
1679
+ /**
1680
+ * Replaces a group at the specified index with a new FormGroup.
1681
+ *
1682
+ * @param index - The index of the group to replace.
1683
+ * @param group - The new FormGroup instance.
1684
+ */
1685
+ replace(index, group) {
1686
+ this.setGroups(old => {
1687
+ const copy = [...old];
1688
+ copy[index] = group;
1689
+ return copy;
1690
+ });
1691
+ }
1692
+ /**
1693
+ * Adds a new FormGroup to the array.
1694
+ *
1695
+ * @param group - The FormGroup instance to add.
1696
+ */
1697
+ add(group) {
1698
+ this.setGroups(old => [...old, group]);
1699
+ }
1700
+ /**
1701
+ * Checks if any validation errors exist.
1702
+ *
1703
+ * @param index - (Optional) The index of the group to check for errors.
1704
+ * If not provided, checks all groups and array-level errors.
1705
+ * @returns `true` if any errors are found, otherwise `false`.
1706
+ */
1707
+ hasError(index) {
1708
+ if (index !== undefined && index !== null) {
1709
+ const group = this.getGroup(index);
1710
+ if (!group) return false;
1711
+ return group.hasAnyError();
1712
+ }
1713
+ const anyGroupError = this.groups.some(g => g.hasAnyError());
1714
+ const anyArrayError = this.arrayErrors.some(e => e.hasError);
1715
+ return anyGroupError || anyArrayError;
1716
+ }
1717
+ /**
1718
+ * Checks if a specific validator error exists for a group at the given index.
1719
+ *
1720
+ * @param index - The index of the group.
1721
+ * @param errKey - The key identifying the specific error to check for.
1722
+ * @returns `true` if the specific error is present, otherwise `false`.
1723
+ */
1724
+ hasValidator(index, errKey) {
1725
+ const group = this.getGroup(index);
1726
+ if (!group) return false;
1727
+ return group.hasAnyValidator(errKey);
1728
+ }
1729
+ /**
1730
+ * Gets all errors for the form array.
1731
+ * Each inner array contains the errors from one FormGroup plus array-level errors.
1732
+ *
1733
+ * @returns An array of error arrays for each group.
1734
+ */
1735
+ getErrors() {
1736
+ return this.groups.map(g => {
1737
+ return [...g.getAllErrors(), ...Clone(this.arrayErrors)];
1738
+ });
1739
+ }
1740
+ /**
1741
+ * Gets errors for a specific group in the array.
1742
+ *
1743
+ * @param index - The index of the group.
1744
+ * @returns An array of errors for the group, or an empty array if not found.
1745
+ */
1746
+ getErrorsAt(index) {
1747
+ const group = this.getGroup(index);
1748
+ if (!group) return [];
1749
+ return [...group.getAllErrors(), ...Clone(this.arrayErrors)];
1750
+ }
1751
+ /**
1752
+ * Gets only array-level errors (not per-group errors).
1753
+ */
1754
+ getArrayErrors() {
1755
+ return Clone(this.arrayErrors);
1756
+ }
1757
+ /**
1758
+ * Validates the form array using current values.
1759
+ *
1760
+ * @param index - Optional index to validate just one group.
1761
+ * @returns True if validation passes, false otherwise.
1762
+ */
1763
+ validateCurrent(index) {
1764
+ return this.validate(index);
1765
+ }
1766
+ /**
1767
+ * Validates the form array.
1768
+ *
1769
+ * @param index - (Optional) The index of a single group to validate.
1770
+ * If not provided, validates all groups and array-level validators.
1771
+ * @returns `true` if all validations pass, otherwise `false`.
1772
+ */
1773
+ validate(index) {
1774
+ // Run array-level validators
1775
+ const values = this.get();
1776
+ this.arrayErrors = this.internalArrayValidation.map(validator => ({
1777
+ key: validator.errKey,
1778
+ hasError: !validator.revalidate(values)
1779
+ }));
1780
+ const arrayValid = this.arrayErrors.every(e => !e.hasError);
1781
+ if (index !== undefined && index !== null) {
1782
+ const group = this.getGroup(index);
1783
+ if (!group) return false;
1784
+ const groupValid = group.validate();
1785
+ return groupValid && arrayValid;
1786
+ }
1787
+ // Validate all groups
1788
+ const groupResults = this.groups.map(g => g.validate());
1789
+ const allGroupsValid = groupResults.every(Boolean);
1790
+ return allGroupsValid && arrayValid;
1791
+ }
1792
+ }
1793
+
1496
1794
  var _tmpl$$j = /*#__PURE__*/template(`<form>`);
1497
1795
  const FormContext = createContext();
1498
1796
  const useFormContext = () => {
1499
1797
  return useContext(FormContext);
1500
1798
  };
1501
1799
  const Form = props => {
1502
- const startData = props.data.get() ?? {};
1800
+ if (props.data instanceof FormArray) {
1801
+ const fa = props.data;
1802
+ return createComponent(ArrayProvider, {
1803
+ formArray: fa,
1804
+ get children() {
1805
+ return createComponent(FormInnerArray, {
1806
+ get onSubmit() {
1807
+ return props.onSubmit;
1808
+ },
1809
+ get children() {
1810
+ return props.children;
1811
+ },
1812
+ formArray: fa
1813
+ });
1814
+ }
1815
+ });
1816
+ }
1817
+ const fg = props.data;
1818
+ const startData = fg.get() ?? {};
1503
1819
  // Custom shallow clone preserving FormArray instances (already handled in FormGroup.get)
1504
1820
  const initialValue = {};
1505
1821
  Object.keys(startData).forEach(k => {
@@ -1507,11 +1823,17 @@ const Form = props => {
1507
1823
  });
1508
1824
  return createComponent(Provider, {
1509
1825
  value: initialValue,
1510
- get formGroup() {
1511
- return props.data;
1512
- },
1826
+ formGroup: fg,
1513
1827
  get children() {
1514
- return createComponent(FormInner, props);
1828
+ return createComponent(FormInner, {
1829
+ data: fg,
1830
+ get onSubmit() {
1831
+ return props.onSubmit;
1832
+ },
1833
+ get children() {
1834
+ return props.children;
1835
+ }
1836
+ });
1515
1837
  }
1516
1838
  });
1517
1839
  };
@@ -1521,7 +1843,7 @@ const FormInner = props => {
1521
1843
  const submitFunction = e => {
1522
1844
  const submitData = CloneStore(formData?.data ?? {});
1523
1845
  e.preventDefault();
1524
- if (formData.formGroup.validate()) {
1846
+ if (formData.formGroup?.validate()) {
1525
1847
  props?.onSubmit?.(submitData);
1526
1848
  }
1527
1849
  };
@@ -1538,6 +1860,27 @@ const FormInner = props => {
1538
1860
  return _el$;
1539
1861
  })();
1540
1862
  };
1863
+ const FormInnerArray = props => {
1864
+ const [formRef, setFormRef] = createSignal();
1865
+ const submitFunction = e => {
1866
+ e.preventDefault();
1867
+ if (props.formArray.validateCurrent()) {
1868
+ props.onSubmit(props.formArray.get());
1869
+ }
1870
+ };
1871
+ createEffect(() => {
1872
+ formRef()?.addEventListener('submit', submitFunction);
1873
+ });
1874
+ onCleanup(() => {
1875
+ formRef()?.removeEventListener('submit', submitFunction);
1876
+ });
1877
+ return (() => {
1878
+ var _el$2 = _tmpl$$j();
1879
+ use(setFormRef, _el$2);
1880
+ insert(_el$2, () => props.children);
1881
+ return _el$2;
1882
+ })();
1883
+ };
1541
1884
  const Provider = props => {
1542
1885
  const defaultData = props.value ?? {};
1543
1886
  const [data, setData] = createStore(defaultData);
@@ -1565,19 +1908,136 @@ const Provider = props => {
1565
1908
  }
1566
1909
  });
1567
1910
  };
1911
+ const ArrayProvider = props => {
1912
+ const [data, setData] = createStore({});
1913
+ return createComponent(FormContext.Provider, {
1914
+ get value() {
1915
+ return {
1916
+ data,
1917
+ setData,
1918
+ formArray: props.formArray
1919
+ };
1920
+ },
1921
+ get children() {
1922
+ return props.children;
1923
+ }
1924
+ });
1925
+ };
1926
+
1927
+ /**
1928
+ * Hook that resolves form binding from direct props, with FormField context as fallback.
1929
+ *
1930
+ * Supports three binding modes:
1931
+ * 1. **FormGroup (simple)**: `formName` binds to `formGroup.data[formName]`
1932
+ * 2. **FormArray (top-level)**: `formIndex` + `formName` binds to `formArray.getAt(formIndex)[formName]`
1933
+ * 3. **FormArray (within FormGroup)**: `formArrayName` + `formIndex` + `formName` binds to
1934
+ * `formGroup.data[formArrayName].getAt(formIndex)[formName]`
1935
+ *
1936
+ * Priority: direct props > FormField context > undefined
1937
+ */
1938
+ function useDirectFormBinding(props) {
1939
+ const fieldContext = useFormProvider();
1940
+ const formContext = useFormContext();
1941
+ const resolvedFormName = props.formName ?? fieldContext?.formName;
1942
+ // Determine which FormArray we're targeting (if any)
1943
+ const resolvedFormArray = createMemo(() => {
1944
+ if (props.formIndex === undefined) return undefined;
1945
+ // Top-level FormArray
1946
+ if (formContext?.formArray) {
1947
+ return formContext.formArray;
1948
+ }
1949
+ // FormArray within FormGroup
1950
+ if (props.formArrayName && formContext?.formGroup) {
1951
+ const field = formContext.formGroup.getR(props.formArrayName);
1952
+ if (field instanceof FormArray) {
1953
+ return field;
1954
+ }
1955
+ }
1956
+ return undefined;
1957
+ });
1958
+ const isFormArray = createMemo(() => !!resolvedFormArray());
1959
+ const getValue = () => {
1960
+ const fa = resolvedFormArray();
1961
+ if (fa && props.formIndex !== undefined && resolvedFormName) {
1962
+ return fa.getProperty(props.formIndex, resolvedFormName);
1963
+ }
1964
+ // Simple FormGroup binding
1965
+ if (resolvedFormName && formContext?.data) {
1966
+ return formContext.data[resolvedFormName];
1967
+ }
1968
+ return undefined;
1969
+ };
1970
+ const setValue = val => {
1971
+ const fa = resolvedFormArray();
1972
+ if (fa && props.formIndex !== undefined && resolvedFormName) {
1973
+ fa.setProperty(props.formIndex, resolvedFormName, val);
1974
+ return;
1975
+ }
1976
+ // Simple FormGroup binding
1977
+ if (resolvedFormName && formContext?.formGroup) {
1978
+ formContext.formGroup.set(resolvedFormName, val);
1979
+ formContext.setData?.(resolvedFormName, val);
1980
+ }
1981
+ };
1982
+ const markDirty = () => {
1983
+ const fa = resolvedFormArray();
1984
+ if (fa && props.formIndex !== undefined && resolvedFormName) {
1985
+ const group = fa.getGroup(props.formIndex);
1986
+ if (group) {
1987
+ group.markDirty(resolvedFormName);
1988
+ }
1989
+ return;
1990
+ }
1991
+ if (resolvedFormName && formContext?.formGroup) {
1992
+ const meta = formContext.formGroup.getMeta(resolvedFormName);
1993
+ if (meta && !meta.dirty) {
1994
+ formContext.formGroup.markDirty(resolvedFormName);
1995
+ }
1996
+ }
1997
+ };
1998
+ const validate = () => {
1999
+ const fa = resolvedFormArray();
2000
+ if (fa && props.formIndex !== undefined) {
2001
+ return fa.validateCurrent(props.formIndex);
2002
+ }
2003
+ if (resolvedFormName && formContext?.formGroup) {
2004
+ return formContext.formGroup.validate(resolvedFormName);
2005
+ }
2006
+ return undefined;
2007
+ };
2008
+ return {
2009
+ formName: resolvedFormName,
2010
+ getValue,
2011
+ setValue,
2012
+ markDirty,
2013
+ validate,
2014
+ formGroup: formContext?.formGroup,
2015
+ formArray: resolvedFormArray(),
2016
+ isFormArray: isFormArray()
2017
+ };
2018
+ }
1568
2019
 
1569
2020
  var _tmpl$$i = /*#__PURE__*/template(`<input>`);
1570
2021
  const Input = props => {
1571
- const [customProps, normalProps] = splitProps(props, ["tooltip", "transparent", "value", "onChange"]);
2022
+ const [customProps, normalProps] = splitProps(props, ["tooltip", "transparent", "value", "onChange", "formName", "formIndex", "formArrayName"]);
1572
2023
  const context = useFormProvider();
1573
2024
  const formContext = useFormContext();
2025
+ const binding = useDirectFormBinding({
2026
+ formName: props.formName,
2027
+ formIndex: props.formIndex,
2028
+ formArrayName: props.formArrayName
2029
+ });
2030
+ const effectiveFormName = binding.formName;
1574
2031
  // Determine the input's value using form context or provided prop value
1575
2032
  const inputValue = createMemo(() => {
1576
2033
  if (!isNullish(props?.value)) {
1577
2034
  return props.value;
1578
2035
  }
1579
- if (!isNullish(formContext?.data?.[context.formName ?? ""])) {
1580
- return formContext.data[context.formName ?? ""];
2036
+ if (binding.isFormArray) {
2037
+ return binding.getValue();
2038
+ }
2039
+ if (!isNullish(formContext?.data?.[effectiveFormName ?? ""])) {
2040
+ return formContext.data[effectiveFormName ?? ""];
1581
2041
  }
1582
2042
  if (!isNullish(context.getValue)) {
1583
2043
  return context.getValue();
@@ -1586,20 +2046,23 @@ const Input = props => {
1586
2046
  });
1587
2047
  const isRequired = createMemo(() => {
1588
2048
  if (context && formContext) {
1589
- const key = context?.formName ?? "";
2049
+ const key = effectiveFormName ?? "";
1590
2050
  return formContext?.formGroup?.hasValidator?.(key, 'required');
1591
2051
  }
1592
2052
  return Object.keys(normalProps).includes("required") && props?.required !== false || props?.required === true;
1593
2053
  });
1594
2054
  // onChange only handles non-checkbox values
1595
2055
  const onChange = e => {
1596
- if (!isNullish(formContext?.setData) && !!formContext?.formGroup?.get) {
2056
+ if (binding.isFormArray) {
2057
+ binding.setValue(e.currentTarget.value);
2058
+ context?.setTextInside?.(!isNullish(e.currentTarget.value) && e.currentTarget.value?.trim() !== "");
2059
+ } else if (!isNullish(formContext?.setData) && !!formContext?.formGroup?.get) {
1597
2060
  const textInside = !isNullish(e.currentTarget.value) && e.currentTarget.value?.trim() !== "";
1598
2061
  context?.setTextInside?.(textInside);
1599
- formContext.formGroup?.set?.(context.formName ?? "", e.currentTarget.value);
2062
+ formContext.formGroup?.set?.(effectiveFormName ?? "", e.currentTarget.value);
1600
2063
  formContext?.setData?.(old => ({
1601
2064
  ...old,
1602
- [context.formName ?? ""]: e.currentTarget.value
2065
+ [effectiveFormName ?? ""]: e.currentTarget.value
1603
2066
  }));
1604
2067
  } else if (!isNullish(context.getName) && !!context?.getName?.()) {
1605
2068
  if (e.currentTarget.value.trim()) {
@@ -1618,7 +2081,7 @@ const Input = props => {
1618
2081
  // Force a non-checkbox field type
1619
2082
  context.setFieldType(props.type === "checkbox" ? "text" : props.type ?? "text");
1620
2083
  if (!isNullish(formContext?.data)) {
1621
- const raw = formContext.data[context.formName ?? ""];
2084
+ const raw = formContext.data[effectiveFormName ?? ""];
1622
2085
  if (typeof raw === 'string') {
1623
2086
  const formValue = raw.trim();
1624
2087
  if (formValue) {
@@ -1680,6 +2143,10 @@ const Input = props => {
1680
2143
  if (props.disabled) return;
1681
2144
  if (!isNullish(context.getName)) {
1682
2145
  context.setFocused?.(true);
2146
+ if (effectiveFormName && formContext?.formGroup?.markDirty) {
2147
+ const meta = formContext.formGroup.getMeta(effectiveFormName);
2148
+ if (meta && !meta.dirty) formContext.formGroup.markDirty(effectiveFormName);
2149
+ }
1683
2150
  }
1684
2151
  },
1685
2152
  "onBlur": e => {
@@ -1687,12 +2154,12 @@ const Input = props => {
1687
2154
  if (!isNullish(context.getName)) {
1688
2155
  context?.setFocused?.(prev => {
1689
2156
  if (prev) {
1690
- formContext?.formGroup?.validate?.(context?.formName ?? "");
2157
+ formContext?.formGroup?.validate?.(effectiveFormName ?? "");
1691
2158
  }
1692
2159
  return false;
1693
2160
  });
1694
2161
  const noValue = !e.currentTarget?.value?.trim();
1695
- const noFormValue = !formContext?.data?.[context?.formName ?? ""];
2162
+ const noFormValue = !formContext?.data?.[effectiveFormName ?? ""];
1696
2163
  const noContextValue = !context?.getValue();
1697
2164
  context.setTextInside(noValue && noFormValue && noContextValue);
1698
2165
  }
@@ -1721,12 +2188,18 @@ var _tmpl$$h = /*#__PURE__*/template(`<label><input type=checkbox><span>`),
1721
2188
  function Checkbox(props) {
1722
2189
  const field = useFormProvider();
1723
2190
  const formCtx = useFormContext();
1724
- const formName = field?.formName;
2191
+ const binding = useDirectFormBinding({
2192
+ formName: props.formName,
2193
+ formIndex: props.formIndex,
2194
+ formArrayName: props.formArrayName
2195
+ });
2196
+ const formName = binding.formName;
1725
2197
  // Internal state for uncontrolled usage outside form context
1726
2198
  const [internalChecked, setInternalChecked] = createSignal(props.defaultChecked ?? false);
1727
- // Derive current checked state with priority: controlled prop -> form context -> field local value -> internal state
2199
+ // Derive current checked state with priority: controlled prop -> FormArray -> form context -> field local value -> internal state
1728
2200
  const checkedState = createMemo(() => {
1729
2201
  if (props.checked !== undefined) return !!props.checked;
2202
+ if (binding.isFormArray) return !!binding.getValue();
1730
2203
  if (formName && formCtx?.data) return !!formCtx.data[formName];
1731
2204
  if (field?.getValue) return !!field.getValue();
1732
2205
  return internalChecked();
@@ -1741,7 +2214,9 @@ function Checkbox(props) {
1741
2214
  });
1742
2215
  const commitValue = next => {
1743
2216
  if (props.checked === undefined) {
1744
- if (formName && formCtx?.formGroup) {
2217
+ if (binding.isFormArray) {
2218
+ binding.setValue(next);
2219
+ } else if (formName && formCtx?.formGroup) {
1745
2220
  formCtx.formGroup.set(formName, next);
1746
2221
  formCtx.setData?.(formName, next);
1747
2222
  } else if (field?.setValue) {
@@ -1856,6 +2331,9 @@ function getEntryAmount() {
1856
2331
  function ignoreWindowManager(element) {
1857
2332
  manager.ignoreElement(element);
1858
2333
  }
2334
+ function unignoreWindowManager(element) {
2335
+ manager.unignoreElement(element);
2336
+ }
1859
2337
  // Unregister the given element.
1860
2338
  function unregisterWindowManager(entry) {
1861
2339
  manager.unregister(entry);
@@ -2081,6 +2559,7 @@ function Select(props) {
2081
2559
  const [selectRef, setSelectRef] = createSignal();
2082
2560
  const [dropdownRef, setDropdownRef] = createSignal();
2083
2561
  const [mobilePopupRef, setMobilePopupRef] = createSignal();
2562
+ let mobileOptionsRef;
2084
2563
  // Resolved rendering mode: 'popup' for fullscreen mobile popup, 'desktop' for traditional dropdown
2085
2564
  const resolvedMode = createMemo(() => {
2086
2565
  const mode = props.mobileMode ?? 'auto';
@@ -2091,11 +2570,17 @@ function Select(props) {
2091
2570
  });
2092
2571
  const form = useFormProvider();
2093
2572
  const formContext = useFormContext();
2094
- const hasForm = !!form?.formName || !!form?.getName || !!form?.getValue || !!form?.setValue || !!form?.getTextInside;
2573
+ const binding = useDirectFormBinding({
2574
+ formName: props.formName,
2575
+ formIndex: props.formIndex,
2576
+ formArrayName: props.formArrayName
2577
+ });
2578
+ const effectiveFormName = binding.formName;
2579
+ const hasForm = !!effectiveFormName || !!effectiveFormName || !!form?.getName || !!form?.getValue || !!form?.setValue || !!form?.getTextInside;
2095
2580
  const hasFormContext = hasForm && !!formContext?.formGroup;
2096
2581
  const currentValue = () => {
2097
2582
  if (hasFormContext) {
2098
- return formContext.data?.[form?.formName ?? ''];
2583
+ return formContext.data?.[effectiveFormName ?? ''];
2099
2584
  }
2100
2585
  if (hasForm) {
2101
2586
  return form.getValue();
@@ -2163,9 +2648,9 @@ function Select(props) {
2163
2648
  // Function to handle selecting/toggling an option for single or multi-select
2164
2649
  // When not using a form or greater FormContext
2165
2650
  const selectValue = val => {
2166
- if (form?.formName && formContext?.formGroup?.markDirty) {
2167
- const meta = formContext.formGroup.getMeta(form?.formName);
2168
- if (meta && !meta.dirty) formContext.formGroup.markDirty(form?.formName);
2651
+ if (effectiveFormName && formContext?.formGroup?.markDirty) {
2652
+ const meta = formContext.formGroup.getMeta(effectiveFormName);
2653
+ if (meta && !meta.dirty) formContext.formGroup.markDirty(effectiveFormName);
2169
2654
  }
2170
2655
  if (isMultiple) {
2171
2656
  // For multi-select, toggle the value in the array
@@ -2176,9 +2661,16 @@ function Select(props) {
2176
2661
  } else {
2177
2662
  newSelected = [...current, val];
2178
2663
  }
2664
+ // Preserve scroll position in mobile popup before reactive updates
2665
+ const savedScrollTop = mobileOptionsRef?.scrollTop;
2179
2666
  setSelected(() => newSelected);
2180
2667
  props?.onChange?.(newSelected);
2181
2668
  props?.onSelect?.(newSelected);
2669
+ if (savedScrollTop != null && mobileOptionsRef) {
2670
+ queueMicrotask(() => {
2671
+ mobileOptionsRef.scrollTop = savedScrollTop;
2672
+ });
2673
+ }
2182
2674
  } else {
2183
2675
  setSelected(() => val);
2184
2676
  props?.onChange?.(val);
@@ -2287,7 +2779,7 @@ function Select(props) {
2287
2779
  setOpen(false);
2288
2780
  form?.setFocused?.(prev => {
2289
2781
  if (prev) {
2290
- formContext?.formGroup?.validate?.(form?.formName ?? '');
2782
+ formContext?.formGroup?.validate?.(effectiveFormName ?? '');
2291
2783
  }
2292
2784
  return false;
2293
2785
  });
@@ -2329,7 +2821,7 @@ function Select(props) {
2329
2821
  setOpen(false);
2330
2822
  form?.setFocused?.(prev => {
2331
2823
  if (prev) {
2332
- formContext?.formGroup?.validate?.(form?.formName ?? '');
2824
+ formContext?.formGroup?.validate?.(effectiveFormName ?? '');
2333
2825
  }
2334
2826
  return false;
2335
2827
  });
@@ -2406,7 +2898,8 @@ function Select(props) {
2406
2898
  isDisabled,
2407
2899
  closeDropdown: () => {
2408
2900
  if (!isMultiple) setOpen(false);
2409
- }
2901
+ },
2902
+ formName: effectiveFormName
2410
2903
  }));
2411
2904
  // --- Keyboard handler ---
2412
2905
  const handleKeyDown = e => {
@@ -2513,6 +3006,10 @@ function Select(props) {
2513
3006
  _el$7.addEventListener("focus", () => {
2514
3007
  if (isDisabled()) return;
2515
3008
  form?.setFocused?.(true);
3009
+ if (effectiveFormName && formContext?.formGroup?.markDirty) {
3010
+ const meta = formContext.formGroup.getMeta(effectiveFormName);
3011
+ if (meta && !meta.dirty) formContext.formGroup.markDirty(effectiveFormName);
3012
+ }
2516
3013
  });
2517
3014
  _el$7._$owner = getOwner();
2518
3015
  _el$8.$$click = e => {
@@ -2608,6 +3105,8 @@ function Select(props) {
2608
3105
  use(setMobilePopupRef, _el$14);
2609
3106
  insert(_el$16, mobilePopupTitle);
2610
3107
  _el$17.$$click = () => closeMobilePopup();
3108
+ var _ref$ = mobileOptionsRef;
3109
+ typeof _ref$ === "function" ? use(_ref$, _el$18) : mobileOptionsRef = _el$18;
2611
3110
  insert(_el$18, () => props.children);
2612
3111
  insert(_el$14, createComponent(Show, {
2613
3112
  when: isMultiple,
@@ -2735,30 +3234,30 @@ function Option(props) {
2735
3234
  };
2736
3235
  const selectFormContextValue = value => {
2737
3236
  if (formContext) {
2738
- const currentValue = formContext.data[formField?.formName ?? ''];
3237
+ const currentValue = formContext.data[select?.formName ?? formField?.formName ?? ''];
2739
3238
  formField?.setFocused?.(true);
2740
3239
  if (Array.isArray(currentValue)) {
2741
3240
  if (currentValue.includes(value)) {
2742
3241
  const setValue = currentValue.filter(val => val !== value);
2743
- formContext.formGroup.set(formField?.formName ?? '', setValue);
3242
+ formContext?.formGroup?.set?.(select?.formName ?? formField?.formName ?? '', setValue);
2744
3243
  formContext.setData(old => ({
2745
3244
  ...old,
2746
- [formField?.formName ?? '']: setValue
3245
+ [select?.formName ?? formField?.formName ?? '']: setValue
2747
3246
  }));
2748
3247
  return true;
2749
3248
  } else {
2750
- formContext.formGroup.set(formField?.formName ?? '', [...currentValue, value]);
3249
+ formContext?.formGroup?.set?.(select?.formName ?? formField?.formName ?? '', [...currentValue, value]);
2751
3250
  formContext.setData(old => ({
2752
3251
  ...old,
2753
- [formField?.formName ?? '']: [...currentValue, value]
3252
+ [select?.formName ?? formField?.formName ?? '']: [...currentValue, value]
2754
3253
  }));
2755
3254
  return true;
2756
3255
  }
2757
3256
  } else {
2758
- formContext.formGroup.set(formField?.formName ?? '', value);
3257
+ formContext?.formGroup?.set?.(select?.formName ?? formField?.formName ?? '', value);
2759
3258
  formContext.setData(old => ({
2760
3259
  ...old,
2761
- [formField?.formName ?? '']: value
3260
+ [select?.formName ?? formField?.formName ?? '']: value
2762
3261
  }));
2763
3262
  return true;
2764
3263
  }
@@ -2770,7 +3269,7 @@ function Option(props) {
2770
3269
  e.stopPropagation();
2771
3270
  const contextSuccess = selectFormContextValue(props.value);
2772
3271
  !contextSuccess ? selectFormFieldValue(props.value) : true;
2773
- if (!formField?.getName?.()) {
3272
+ if (!formField?.getName?.() && !select?.formName) {
2774
3273
  select.selectValue?.(props.value);
2775
3274
  } else {
2776
3275
  // Form handled the value — still need to close for single select
@@ -2825,14 +3324,23 @@ styleInject(css_248z$b);
2825
3324
  var _tmpl$$e = /*#__PURE__*/template(`<textarea>`);
2826
3325
  const TextArea = props => {
2827
3326
  let myElement;
2828
- const [customProps, normalProps] = splitProps(props, ["minSize", "text", "setText", "class", "tooltip", "transparent"]);
3327
+ const [customProps, normalProps] = splitProps(props, ["minSize", "text", "setText", "class", "tooltip", "transparent", "formName", "formIndex", "formArrayName"]);
2829
3328
  const fieldCtx = useFormProvider();
2830
3329
  const formCtx = useFormContext();
2831
- const formName = fieldCtx?.formName;
3330
+ const binding = useDirectFormBinding({
3331
+ formName: props.formName,
3332
+ formIndex: props.formIndex,
3333
+ formArrayName: props.formArrayName
3334
+ });
3335
+ const formName = binding.formName;
2832
3336
  // Internal state only used when not provided externally and not in form context.
2833
3337
  const [internal, setInternal] = createSignal(customProps.text ? customProps.text() : "");
2834
- // Determine current value priority: FormGroup -> external accessor -> internal state
3338
+ // Determine current value priority: FormArray -> FormGroup -> external accessor -> internal state
2835
3339
  const areaValue = createMemo(() => {
3340
+ if (binding.isFormArray) {
3341
+ const val = binding.getValue();
3342
+ return val !== undefined && val !== null ? String(val) : '';
3343
+ }
2836
3344
  if (formName && formCtx?.data && !isNullish(formCtx.data[formName])) {
2837
3345
  return formCtx.data[formName];
2838
3346
  }
@@ -2886,7 +3394,9 @@ const TextArea = props => {
2886
3394
  if (props.disabled) return;
2887
3395
  const newVal = e.currentTarget.value;
2888
3396
  // Update whichever source is active
2889
- if (formName && formCtx?.formGroup) {
3397
+ if (binding.isFormArray) {
3398
+ binding.setValue(newVal);
3399
+ } else if (formName && formCtx?.formGroup) {
2890
3400
  formCtx.formGroup.set(formName, newVal);
2891
3401
  formCtx.setData?.(formName, newVal);
2892
3402
  } else if (customProps.setText) {
@@ -3593,19 +4103,27 @@ function RadioGroup(props) {
3593
4103
  radioGroupCount++;
3594
4104
  const groupName = props.name ?? `radio-group-${radioGroupCount}`;
3595
4105
  const [internalValue, setInternalValue] = createSignal(props.defaultValue);
3596
- const formField = useFormProvider();
4106
+ useFormProvider();
3597
4107
  const formContext = useFormContext();
4108
+ const binding = useDirectFormBinding({
4109
+ formName: props.formName,
4110
+ formIndex: props.formIndex,
4111
+ formArrayName: props.formArrayName
4112
+ });
4113
+ const effectiveFormName = binding.formName;
3598
4114
  const selectedValue = () => props.value !== undefined ? props.value : internalValue();
3599
4115
  const setSelectedValue = val => {
3600
4116
  if (props.value === undefined) {
3601
4117
  setInternalValue(val);
3602
4118
  }
3603
- // Bridge to FormGroup if inside a FormField with formName
3604
- if (formField?.formName && formContext?.formGroup?.set) {
3605
- formContext.formGroup.set(formField.formName, val);
4119
+ // Bridge to FormGroup/FormArray
4120
+ if (binding.isFormArray && effectiveFormName) {
4121
+ binding.setValue(val);
4122
+ } else if (effectiveFormName && formContext?.formGroup?.set) {
4123
+ formContext.formGroup.set(effectiveFormName, val);
3606
4124
  formContext.setData(old => ({
3607
4125
  ...old,
3608
- [formField.formName]: val
4126
+ [effectiveFormName]: val
3609
4127
  }));
3610
4128
  }
3611
4129
  props.onChange?.(val);
@@ -3798,334 +4316,6 @@ function Radio(props) {
3798
4316
  }
3799
4317
  delegateEvents(["keydown"]);
3800
4318
 
3801
- /**
3802
- * A class that encapsulates an array of form controls along with their validation logic.
3803
- *
3804
- * @template T - An object type representing the shape of a single form control's value.
3805
- */
3806
- class FormArray {
3807
- /**
3808
- * An array of array-level validators. Each validator validates the entire array of form control values.
3809
- * Each element is a ValidatorResult function that returns a validation result for the entire array.
3810
- *
3811
- * @private
3812
- */
3813
- internalArrayValidation;
3814
- /**
3815
- * An array of control-level validators. Each element is a tuple where the first value is not used
3816
- * in this class and the second value is an array of validators that validate a single control value.
3817
- *
3818
- * @private
3819
- */
3820
- internalValidation;
3821
- /**
3822
- * A function to update the current state of control-level validations.
3823
- *
3824
- * @private
3825
- */
3826
- setValidation;
3827
- /**
3828
- * An array of error arrays. Each inner array holds the errors corresponding to a single form control.
3829
- *
3830
- * @private
3831
- */
3832
- errors = [];
3833
- /**
3834
- * The current values of the form controls in the array.
3835
- *
3836
- * @private
3837
- */
3838
- internalValues;
3839
- /**
3840
- * A function to update the current values of the form controls.
3841
- *
3842
- * @private
3843
- */
3844
- setValues;
3845
- // Snapshots of initial validation defs & values for reset purposes
3846
- _initialValidationDefs;
3847
- _initialValuesSnapshot;
3848
- /**
3849
- * Creates an instance of FormArray.
3850
- *
3851
- * @param arrayValidation - A tuple where the first element is an initial array of control-level validation definitions
3852
- * and the second element is an array of array-level validators.
3853
- * @param initialValues - Optional initial values for the form controls.
3854
- */
3855
- constructor(arrayValidation, initialValues = []) {
3856
- const [valid, setValid] = createStore(arrayValidation[0]);
3857
- const [values, setValues] = createStore(initialValues);
3858
- this.internalArrayValidation = arrayValidation[1];
3859
- this.internalValidation = valid;
3860
- this.setValidation = setValid;
3861
- this.internalValues = values;
3862
- this.setValues = setValues;
3863
- this._initialValidationDefs = [...arrayValidation[0]];
3864
- this._initialValuesSnapshot = Array.isArray(initialValues) ? Clone(initialValues) : Clone([initialValues]);
3865
- }
3866
- /**
3867
- * Returns true if any validator (array-level or control-level) exists matching the provided errKey.
3868
- * If no errKey provided, returns true when any validator exists at all.
3869
- */
3870
- hasAnyValidator(errKey) {
3871
- if (!errKey) {
3872
- return this.internalArrayValidation.length > 0 || this.internalValidation.some(v => v[1].length > 0);
3873
- }
3874
- const inArray = this.internalArrayValidation.some(v => v.errKey === errKey);
3875
- if (inArray) return true;
3876
- return this.internalValidation.some(v => v[1].some(val => val.errKey === errKey));
3877
- }
3878
- /**
3879
- * Resets the array to its initial validation definitions and values.
3880
- * Clears any accumulated errors and added dynamic controls.
3881
- */
3882
- reset() {
3883
- this.setValidation(() => [...this._initialValidationDefs]);
3884
- this.setValues(() => Clone(this._initialValuesSnapshot));
3885
- this.errors = [];
3886
- }
3887
- /**
3888
- * Gets the current values of the form controls in the array.
3889
- *
3890
- * @returns A cloned copy of the values array.
3891
- */
3892
- get() {
3893
- // Return a cloned snapshot so external mutation of returned array/value objects
3894
- // can't silently mutate internal reactive state or bypass validation/meta tracking.
3895
- return Clone(this.internalValues);
3896
- }
3897
- /**
3898
- * Gets the value of a specific control in the array.
3899
- *
3900
- * @param index - The index of the control.
3901
- * @returns A cloned copy of the control value.
3902
- */
3903
- getAt(index) {
3904
- if (index < 0 || index >= this.internalValues.length) {
3905
- return undefined;
3906
- }
3907
- return CloneStore(this.internalValues[index]);
3908
- }
3909
- /**
3910
- * Gets a property value from an object at the specified index.
3911
- *
3912
- * @param index - The index of the control.
3913
- * @param key - The property key to retrieve.
3914
- * @returns The property value, or undefined if not found.
3915
- */
3916
- getProperty(index, key) {
3917
- const item = this.getAt(index);
3918
- return item ? item[key] : undefined;
3919
- }
3920
- /**
3921
- * Sets the values of all controls in the array.
3922
- *
3923
- * @param values - The new values for the controls.
3924
- */
3925
- set(values) {
3926
- this.setValues(Clone(values));
3927
- }
3928
- /**
3929
- * Sets the value of a control at the specified index.
3930
- *
3931
- * @param index - The index of the control to update.
3932
- * @param value - The new value for the control.
3933
- */
3934
- setAt(index, value) {
3935
- if (index < 0 || index >= this.internalValues.length) {
3936
- console.error(`Index ${index} out of bounds.`);
3937
- return;
3938
- }
3939
- this.setValues(index, Clone(value));
3940
- }
3941
- /**
3942
- * Sets a property value on an object at the specified index.
3943
- *
3944
- * @param index - The index of the control.
3945
- * @param key - The property key to set.
3946
- * @param value - The new value for the property.
3947
- */
3948
- setProperty(index, key, value) {
3949
- if (index < 0 || index >= this.internalValues.length) {
3950
- console.error(`Index ${index} out of bounds.`);
3951
- return;
3952
- }
3953
- this.setValues(index, oldValue => {
3954
- const newValue = {
3955
- ...oldValue
3956
- };
3957
- newValue[key] = Clone(value);
3958
- return newValue;
3959
- });
3960
- }
3961
- /**
3962
- * Removes a control at the specified index.
3963
- *
3964
- * @param index - The index of the control to remove.
3965
- */
3966
- remove(index) {
3967
- // Remove validation
3968
- this.setValidation(old => {
3969
- const copy = [...old];
3970
- copy.splice(index, 1);
3971
- return copy;
3972
- });
3973
- // Remove value
3974
- this.setValues(old => {
3975
- const copy = [...old];
3976
- copy.splice(index, 1);
3977
- return copy;
3978
- });
3979
- // Remove errors
3980
- if (this.errors.length > index) {
3981
- this.errors.splice(index, 1);
3982
- }
3983
- }
3984
- /**
3985
- * Replaces a control at the specified index with new validation and value.
3986
- *
3987
- * @param index - The index of the control to replace.
3988
- * @param controlValidation - The new validation to apply for the control.
3989
- * @param value - The new value for the control.
3990
- */
3991
- replace(index, controlValidation, value) {
3992
- this.setValidation(old => {
3993
- const copy = [...old];
3994
- copy.splice(index, 1, controlValidation);
3995
- return copy;
3996
- });
3997
- if (value !== undefined) {
3998
- this.setValues(index, Clone(value));
3999
- }
4000
- }
4001
- /**
4002
- * Adds a new control to the array with validation and optional value.
4003
- *
4004
- * @param controlValidation - The validation definition to add.
4005
- * @param value - Optional value for the new control.
4006
- */
4007
- add(controlValidation, value) {
4008
- this.setValidation(old => {
4009
- return [...old, controlValidation];
4010
- });
4011
- this.setValues(old => {
4012
- return [...old, value ? Clone(value) : {}];
4013
- });
4014
- }
4015
- /**
4016
- * Checks if any validation errors exist.
4017
- *
4018
- * @param index - (Optional) The index of the control to check for errors.
4019
- * If not provided, it checks the entire form array.
4020
- * @returns `true` if any errors are found, otherwise `false`.
4021
- */
4022
- hasError(index) {
4023
- if (isNullish(index)) {
4024
- // Check errors for all controls if no index is provided.
4025
- return this.errors.flat().some(error => error.hasError);
4026
- }
4027
- // Check errors for a specific control.
4028
- return this.errors[index].some(error => error.hasError);
4029
- }
4030
- /**
4031
- * Checks if a specific validator error exists for a control at the given index.
4032
- *
4033
- * @param index - The index of the control.
4034
- * @param errKey - The key identifying the specific error to check for.
4035
- * @returns `true` if the specific error is present, otherwise `false`.
4036
- */
4037
- hasValidator(index, errKey) {
4038
- return this.errors[index].some(error => error.key === errKey);
4039
- }
4040
- /**
4041
- * Gets all errors for the form array.
4042
- *
4043
- * @returns An array of error arrays for each control.
4044
- */
4045
- getErrors() {
4046
- return Clone(this.errors);
4047
- }
4048
- /**
4049
- * Gets errors for a specific control in the array.
4050
- *
4051
- * @param index - The index of the control.
4052
- * @returns An array of errors for the control, or an empty array if not found.
4053
- */
4054
- getErrorsAt(index) {
4055
- if (index < 0 || index >= this.errors.length) {
4056
- return [];
4057
- }
4058
- return Clone(this.errors[index]);
4059
- }
4060
- /**
4061
- * Validates the form array using current values.
4062
- *
4063
- * @param index - Optional index to validate just one control.
4064
- * @returns True if validation passes, false otherwise.
4065
- */
4066
- validateCurrent(index) {
4067
- return this.validate(this.internalValues, index);
4068
- }
4069
- /**
4070
- * Validates the provided form control values.
4071
- *
4072
- * @param values - The array of form control values.
4073
- * @param index - (Optional) The index of a single control to validate.
4074
- * If not provided, the method validates all controls in the form array.
4075
- * @returns `true` if all validations pass, otherwise `false`.
4076
- */
4077
- validate(values, index) {
4078
- if (isNullish(values)) {
4079
- console.error('Data is null');
4080
- return false;
4081
- }
4082
- // Validate all controls in the form array.
4083
- if (isNullish(index)) {
4084
- // Always evaluate array-level validators even if the array is empty
4085
- const arrayLevelErrors = this.internalArrayValidation.map(validator => ({
4086
- key: validator.errKey,
4087
- hasError: !validator.revalidate(values)
4088
- }));
4089
- if (values.length === 0) {
4090
- this.errors = [arrayLevelErrors]; // store array-level errors at index 0 placeholder
4091
- return this.errors.flat().every(error => !error.hasError);
4092
- }
4093
- this.errors = values.map((value, i) => {
4094
- const controlErrors = this.internalValidation.map(([propKey, validators]) => {
4095
- const currentVal = value[propKey];
4096
- return validators.map(validator => ({
4097
- key: validator.errKey,
4098
- hasError: !validator.revalidate(currentVal)
4099
- }));
4100
- });
4101
- const arrayErrors = this.internalArrayValidation.map(validator => ({
4102
- key: validator.errKey,
4103
- hasError: !validator.revalidate(values)
4104
- }));
4105
- const merged = [...controlErrors.flat(), ...arrayErrors];
4106
- this.errors[i] = merged;
4107
- return merged;
4108
- });
4109
- return this.errors.flat().every(error => !error.hasError);
4110
- }
4111
- // Validate a specific control in the form array.
4112
- const value = values[index];
4113
- const controlErrors = this.internalValidation.map(([propKey, validators]) => {
4114
- const currentVal = value[propKey];
4115
- return validators.map(validator => ({
4116
- key: validator.errKey,
4117
- hasError: !validator.revalidate(currentVal)
4118
- }));
4119
- });
4120
- const arrayErrors = this.internalArrayValidation.map(validator => ({
4121
- key: validator.errKey,
4122
- hasError: !validator.revalidate(values)
4123
- }));
4124
- this.errors[index] = [...controlErrors.flat(), ...arrayErrors];
4125
- return this.errors[index].every(error => !error.hasError);
4126
- }
4127
- }
4128
-
4129
4319
  var css_248z$2 = ".formfield-module_formField__w6mH9:focus-within > legend, .formfield-module_has-focus__9ingx > legend {\n top: -8px !important;\n font-size: 0.75rem !important;\n}\n\n.formfield-module_formField__w6mH9 {\n position: relative;\n display: inline-block;\n border-radius: var(--border-radius-md);\n box-sizing: border-box;\n border: none;\n background-color: var(--container-color, #ffffff);\n color: var(--on-container-color, #000);\n margin-top: 0px !important;\n margin-bottom: 0px !important;\n}\n.formfield-module_formField__w6mH9 > legend {\n position: absolute;\n left: 12px;\n top: calc(50% - 0.5em);\n font-size: 1rem;\n transition: top 0.25s ease, font-size 0.25s ease;\n pointer-events: none;\n}\n.formfield-module_formField__w6mH9 input::-moz-placeholder, .formfield-module_formField__w6mH9 select::-moz-placeholder {\n opacity: 0 !important;\n -moz-transition: opacity 0.25s ease;\n transition: opacity 0.25s ease;\n}\n.formfield-module_formField__w6mH9 input::placeholder,\n.formfield-module_formField__w6mH9 select::placeholder {\n opacity: 0 !important;\n transition: opacity 0.25s ease;\n}\n.formfield-module_formField__w6mH9 label {\n position: relative;\n top: -0.5rem;\n padding-right: 10px;\n}\n.formfield-module_formField__w6mH9:has(textarea) {\n position: relative;\n margin: 8px;\n}\n.formfield-module_formField__w6mH9:has(textarea) textarea {\n background-color: var(--container-color, #ffffff);\n color: var(--on-container-color, #000);\n width: auto;\n min-width: 95%;\n font-family: \"Roboto, sans-serif\";\n font-size: 16px;\n padding-bottom: 8px;\n border: none;\n}\n.formfield-module_formField__w6mH9:has(textarea) textarea:focus {\n outline: none;\n}\n.formfield-module_formField__w6mH9:has(textarea) textarea::-moz-placeholder {\n color: var(--on-container-color, #000);\n opacity: var(--text-emphasis-medium, 87%);\n}\n.formfield-module_formField__w6mH9:has(textarea) textarea::placeholder {\n color: var(--on-container-color, #000);\n opacity: var(--text-emphasis-medium, 87%);\n}\n.formfield-module_formField__w6mH9:has(textarea) legend {\n height: 24px;\n position: absolute;\n left: 8px;\n transition: all 0.25s ease;\n}\n.formfield-module_formField__w6mH9:has([is=coles-solid-select]) {\n position: relative;\n height: 48px !important;\n padding: 0px !important;\n width: 100%;\n}\n.formfield-module_formField__w6mH9:has([is=coles-solid-select]) legend {\n height: 16px;\n max-height: 16px;\n transition: all 0.25s ease;\n top: calc(50% - 1em);\n z-index: 1;\n}\n.formfield-module_formField__w6mH9:has([is=coles-solid-select]) [is=coles-solid-select] {\n position: absolute;\n left: 0px;\n right: 0px;\n padding: 0px !important;\n margin: 0px !important;\n height: 48px !important;\n border-radius: 4px;\n font-family: \"Roboto, sans-serif\";\n font-size: 16px;\n width: auto;\n min-width: 95%;\n z-index: 0;\n}\n.formfield-module_formField__w6mH9:has([is=coles-solid-select]) [is=coles-solid-select]:nth-child(n) {\n border-radius: 4px;\n}\n.formfield-module_formField__w6mH9:has(input[type=text]) {\n height: 48px;\n position: relative;\n width: 100%;\n margin-top: 16px;\n margin-bottom: 16px;\n}\n.formfield-module_formField__w6mH9:has(input[type=text]) legend {\n position: absolute;\n height: 24px;\n left: 8px;\n transition: all 0.25s ease;\n}\n.formfield-module_formField__w6mH9:has(input[type=text]) input[type=text] {\n background-color: transparent !important;\n height: 24px;\n border-radius: 4px;\n margin: 8px 0px;\n font-family: \"Roboto, sans-serif\";\n font-size: 16px;\n width: 100%;\n min-width: 95%;\n}\n.formfield-module_formField__w6mH9:has(input[type=text]) input[type=text]::-moz-placeholder {\n opacity: 0;\n}\n.formfield-module_formField__w6mH9:has(input[type=text]) input[type=text]::placeholder {\n opacity: 0;\n}\n.formfield-module_formField__w6mH9:has(input[type=number]) {\n height: 48px;\n position: relative;\n width: 100%;\n margin-top: 16px;\n margin-bottom: 16px;\n}\n.formfield-module_formField__w6mH9:has(input[type=number]) legend {\n height: 24px;\n position: absolute;\n left: 8px;\n transition: all 0.25s ease;\n}\n.formfield-module_formField__w6mH9:has(input[type=number]) input[type=number] {\n background-color: transparent !important;\n color: var(--on-primary-color, #fff);\n height: 24px;\n border-radius: 4px;\n margin: 8px 0px;\n font-family: \"Roboto, sans-serif\";\n font-size: 16px;\n width: 100%;\n min-width: 95%;\n}\n.formfield-module_formField__w6mH9:has(input[type=number]) input[type=number]::-moz-placeholder {\n opacity: 0;\n}\n.formfield-module_formField__w6mH9:has(input[type=number]) input[type=number]::placeholder {\n opacity: 0;\n}\n@media screen and (max-width: 768px) {\n .formfield-module_formField__w6mH9 {\n max-width: 75%;\n }\n}\n\n.formfield-module_topMargin__dahW5 {\n margin-top: 12px;\n}\n\n.formfield-module_primary__7BYyQ:has(input[type=text]) input[type=text] {\n color: var(--on-container-color, #000);\n}\n.formfield-module_primary__7BYyQ:has(input[type=text]) input[type=text]::-moz-placeholder {\n color: var(--on-container-color, #000);\n opacity: var(--text-emphasis-medium, 87%);\n}\n.formfield-module_primary__7BYyQ:has(input[type=text]) input[type=text]::placeholder {\n color: var(--on-container-color, #000);\n opacity: var(--text-emphasis-medium, 87%);\n}\n.formfield-module_primary__7BYyQ:has(input[type=number]) input[type=number] {\n color: var(--on-container-color, #000);\n}\n.formfield-module_primary__7BYyQ:has(input[type=number]) input[type=number]::-moz-placeholder {\n color: var(--on-container-color, #000);\n opacity: var(--text-emphasis-medium, 87%);\n}\n.formfield-module_primary__7BYyQ:has(input[type=number]) input[type=number]::placeholder {\n color: var(--on-container-color, #000);\n opacity: var(--text-emphasis-medium, 87%);\n}\n.formfield-module_primary__7BYyQ:has([is=coles-solid-select]) [is=coles-solid-select] {\n background-color: var(--container-color, #ffffff);\n color: var(--on-container-color, #000);\n}\n.formfield-module_primary__7BYyQ:has(textarea) {\n width: 100%;\n height: auto;\n}\n.formfield-module_primary__7BYyQ:has(textarea) textarea {\n background-color: var(--container-color, #ffffff);\n color: var(--on-container-color, #000);\n}\n.formfield-module_primary__7BYyQ:has(textarea) textarea::-moz-placeholder {\n color: var(--on-container-color, #000);\n opacity: var(--text-emphasis-medium, 87%);\n}\n.formfield-module_primary__7BYyQ:has(textarea) textarea::placeholder {\n color: var(--on-container-color, #000);\n opacity: var(--text-emphasis-medium, 87%);\n}\n\n.formfield-module_legendStyle__gnI49 {\n background-color: transparent;\n opacity: var(--text-emphasis-medium, 87%);\n width: 100%;\n transition: all 0.3s ease;\n}\n\n.formfield-module_checkboxPadding__DU1CG {\n padding: 0px;\n padding-bottom: 10px;\n padding-left: 10px;\n padding-right: 10px;\n}\n\n.formfield-module_errorContainer__HR4PL {\n position: relative;\n min-height: 2rem;\n}\n\n.formfield-module_error__wNBh2 {\n position: absolute;\n background: transparent;\n color: var(--error-color, #B00020);\n font-size: 10px;\n}";
4130
4320
  var style = {"formField":"formfield-module_formField__w6mH9","has-focus":"formfield-module_has-focus__9ingx","primary":"formfield-module_primary__7BYyQ","legendStyle":"formfield-module_legendStyle__gnI49","errorContainer":"formfield-module_errorContainer__HR4PL","error":"formfield-module_error__wNBh2"};
4131
4321
  styleInject(css_248z$2);
@@ -4156,16 +4346,6 @@ const FormField2 = props => {
4156
4346
  if (Array.isArray(val) && val.length === 0) return false;
4157
4347
  return true;
4158
4348
  };
4159
- // Track dirty meta so legend can float once a field has been modified even if some components
4160
- // didn't properly synchronize their internal value signals yet.
4161
- const isDirty = () => {
4162
- if (!local?.formName) return false;
4163
- try {
4164
- return !!formContext?.formGroup?.getMeta?.(local.formName)?.dirty;
4165
- } catch {
4166
- return false;
4167
- }
4168
- };
4169
4349
  const shouldFloat = createMemo(() => {
4170
4350
  let currentVal;
4171
4351
  const hasFormContext = !!local?.formName && !!formContext?.formGroup;
@@ -4185,7 +4365,7 @@ const FormField2 = props => {
4185
4365
  // Float when there is a value, or focused, or meta marked dirty (programmatic set),
4186
4366
  // or legacy input marked dirty-on-focus even if still empty (metaDirty && no value yet)
4187
4367
  const hasAnyValue = hasValue(currentVal);
4188
- return hasAnyValue || context?.getFocused?.() || metaDirty && hasAnyValue || isDirty() && hasAnyValue;
4368
+ return hasAnyValue || context?.getFocused?.() || metaDirty;
4189
4369
  });
4190
4370
  // Effect: if underlying meta resets to pristine and value cleared, ensure focused state cleared
4191
4371
  createEffect(() => {
@@ -4202,7 +4382,7 @@ const FormField2 = props => {
4202
4382
  const theChildren = children(() => props.children);
4203
4383
  const formErrors = () => {
4204
4384
  if (!local?.formName) return [];
4205
- const allErrors = (formContext?.formGroup.getErrors(local.formName) ?? []).filter(e => e.hasError);
4385
+ const allErrors = (formContext?.formGroup?.getErrors?.(local.formName) ?? []).filter(e => e.hasError);
4206
4386
  if (allErrors.length === 0) return [];
4207
4387
  let errKeys = allErrors.map(e => e.key);
4208
4388
  if (errKeys.includes('required')) {
@@ -4459,6 +4639,19 @@ class FormGroup {
4459
4639
  errors;
4460
4640
  meta = {};
4461
4641
  keys = [];
4642
+ /** Reactive memo that returns a cloned snapshot of the full form state.
4643
+ * FormArray fields resolve via `.get()` (plain array), others via `CloneStore`. */
4644
+ formChangeValue;
4645
+ /** Returns a per-field reactive accessor. Each call creates an independent memo. */
4646
+ fieldChangeValue = key => {
4647
+ return createMemo(() => {
4648
+ const val = this.internalDataSignal[0][key];
4649
+ if (val instanceof FormArray) {
4650
+ return val.get();
4651
+ }
4652
+ return CloneStore(val);
4653
+ });
4654
+ };
4462
4655
  constructor(data) {
4463
4656
  this.data = data;
4464
4657
  const newData = {};
@@ -4489,6 +4682,20 @@ class FormGroup {
4489
4682
  this.internalDataSignal = createStore(newData);
4490
4683
  this.validators = newValidators;
4491
4684
  this.errors = createSignal(newErrors);
4685
+ // Initialise formChangeValue here (after internalDataSignal exists) to avoid
4686
+ // eager evaluation in Solid dev mode when createMemo is used as a field initialiser.
4687
+ this.formChangeValue = createMemo(() => {
4688
+ const current = this.internalDataSignal[0];
4689
+ return this.keys.reduce((acc, key) => {
4690
+ const val = current[key];
4691
+ if (val instanceof FormArray) {
4692
+ acc[key] = val.get();
4693
+ } else {
4694
+ acc[key] = CloneStore(val);
4695
+ }
4696
+ return acc;
4697
+ }, {});
4698
+ });
4492
4699
  }
4493
4700
  /**
4494
4701
  * INTERNAL: returns the reactive internal store reference (DO NOT MUTATE outside FormGroup).
@@ -4629,6 +4836,42 @@ class FormGroup {
4629
4836
  markDirty(key) {
4630
4837
  this.meta[key].dirty = true;
4631
4838
  }
4839
+ /** Returns true if any field in the group has validation errors. */
4840
+ hasAnyError() {
4841
+ const errs = this.errors[0]();
4842
+ for (const k of this.keys) {
4843
+ const val = this.internalDataSignal[0][k];
4844
+ if (val instanceof FormArray) {
4845
+ if (val.hasError()) return true;
4846
+ } else if (errs[k]?.some(e => e.hasError)) {
4847
+ return true;
4848
+ }
4849
+ }
4850
+ return false;
4851
+ }
4852
+ /** Returns true if any field has a validator, optionally matching errKey. */
4853
+ hasAnyValidator(errKey) {
4854
+ for (const k of this.keys) {
4855
+ const val = this.internalDataSignal[0][k];
4856
+ if (val instanceof FormArray) {
4857
+ if (val.hasAnyValidator(errKey)) return true;
4858
+ } else if (!errKey) {
4859
+ if (this.validators[k]?.length > 0) return true;
4860
+ } else {
4861
+ if (this.validators[k]?.some(v => v.errKey === errKey)) return true;
4862
+ }
4863
+ }
4864
+ return false;
4865
+ }
4866
+ /** Returns a flat array of all field errors across the group. */
4867
+ getAllErrors() {
4868
+ const errs = this.errors[0]();
4869
+ const result = [];
4870
+ for (const k of this.keys) {
4871
+ if (errs[k]) result.push(...errs[k]);
4872
+ }
4873
+ return result;
4874
+ }
4632
4875
  reset() {
4633
4876
  for (const k of this.keys) {
4634
4877
  const m = this.meta[k];
@@ -4675,15 +4918,14 @@ class FormGroup {
4675
4918
  }
4676
4919
  }
4677
4920
  /**
4678
- * Adds an item to a FormArray control
4921
+ * Adds a FormGroup item to a FormArray control.
4679
4922
  *
4680
4923
  * @param key - The key of the FormArray control
4681
- * @param controlValidation - The validation for the new item
4682
- * @param value - Optional initial value for the new item
4924
+ * @param group - The FormGroup instance to add
4683
4925
  */
4684
- addToArray(key, controlValidation, value) {
4926
+ addToArray(key, group) {
4685
4927
  if (this.internalDataSignal[0][key] instanceof FormArray) {
4686
- this.internalDataSignal[0][key].add(controlValidation, value);
4928
+ this.internalDataSignal[0][key].add(group);
4687
4929
  }
4688
4930
  }
4689
4931
  /**
@@ -4766,6 +5008,24 @@ class FormGroup {
4766
5008
  }
4767
5009
  }
4768
5010
 
5011
+ function useFormFieldBinding(key) {
5012
+ const ctx = useFormContext();
5013
+ if (!ctx.formGroup) {
5014
+ throw new Error('useFormFieldBinding requires a FormGroup. It cannot be used with FormArray as the top-level Form data.');
5015
+ }
5016
+ const fg = ctx.formGroup;
5017
+ const value = createMemo(() => fg.getR(key));
5018
+ return {
5019
+ value,
5020
+ setValue: v => fg.set(key, v),
5021
+ errors: () => fg.getErrors(key) ?? [],
5022
+ hasError: () => fg.hasError(key),
5023
+ touched: () => fg.getMeta(key)?.touched,
5024
+ dirty: () => fg.getMeta(key)?.dirty,
5025
+ validate: () => fg.validate(key)
5026
+ };
5027
+ }
5028
+
4769
5029
  function createTableContext() {
4770
5030
  return createContext({});
4771
5031
  }
@@ -5813,4 +6073,4 @@ const ColeError = props => {
5813
6073
  return [];
5814
6074
  };
5815
6075
 
5816
- export { Body, Button, Carousel, Cell, Checkbox, Chip, Chipbar, Column, Container, ExpansionPanel, ColeError as FieldError, Form, FormArray, FormField, FormGroup, Header, Icon, Input, Menu, MenuDropdown, MenuItem, Modal, Option, Radio, RadioGroup, RadioGroupContext, Row, Select, Slide, SnackbarController, TabBar, Table, TextArea, Validators, addSnackbar, addTheme, carouselTitleContext };
6076
+ export { Body, Button, Carousel, Cell, Checkbox, Chip, Chipbar, Column, Container, ExpansionPanel, ColeError as FieldError, Form, FormArray, FormField, FormGroup, Header, Icon, Input, Menu, MenuDropdown, MenuItem, Modal, Option, Radio, RadioGroup, RadioGroupContext, Row, Select, Slide, SnackbarController, TabBar, Table, TextArea, Validators, addSnackbar, addTheme, carouselTitleContext, getEntryAmount, ignoreWindowManager, registerWindowManager, unignoreWindowManager, unregisterWindowManager, useDirectFormBinding, useFormFieldBinding };