@dmitryvim/form-builder 0.2.16 → 0.2.18

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/esm/index.js CHANGED
@@ -719,6 +719,21 @@ function updateTextField(element, fieldPath, value, context) {
719
719
  }
720
720
 
721
721
  // src/components/textarea.ts
722
+ function applyAutoExpand(textarea) {
723
+ textarea.style.overflow = "hidden";
724
+ textarea.style.resize = "none";
725
+ const lineCount = (textarea.value.match(/\n/g) || []).length + 1;
726
+ textarea.rows = Math.max(1, lineCount);
727
+ const resize = () => {
728
+ if (!textarea.isConnected) return;
729
+ textarea.style.height = "0";
730
+ textarea.style.height = `${textarea.scrollHeight}px`;
731
+ };
732
+ textarea.addEventListener("input", resize);
733
+ setTimeout(() => {
734
+ if (textarea.isConnected) resize();
735
+ }, 0);
736
+ }
722
737
  function renderTextareaElement(element, ctx, wrapper, pathKey) {
723
738
  const state = ctx.state;
724
739
  const textareaWrapper = document.createElement("div");
@@ -739,6 +754,9 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
739
754
  textareaInput.addEventListener("blur", handleChange);
740
755
  textareaInput.addEventListener("input", handleChange);
741
756
  }
757
+ if (element.autoExpand || state.config.readonly) {
758
+ applyAutoExpand(textareaInput);
759
+ }
742
760
  textareaWrapper.appendChild(textareaInput);
743
761
  if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
744
762
  const counter = createCharCounter(element, textareaInput, true);
@@ -787,6 +805,9 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
787
805
  textareaInput.addEventListener("blur", handleChange);
788
806
  textareaInput.addEventListener("input", handleChange);
789
807
  }
808
+ if (element.autoExpand || state.config.readonly) {
809
+ applyAutoExpand(textareaInput);
810
+ }
790
811
  textareaContainer.appendChild(textareaInput);
791
812
  if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
792
813
  const counter = createCharCounter(element, textareaInput, true);
@@ -888,6 +909,24 @@ function validateTextareaElement(element, key, context) {
888
909
  }
889
910
  function updateTextareaField(element, fieldPath, value, context) {
890
911
  updateTextField(element, fieldPath, value, context);
912
+ const { scopeRoot, state } = context;
913
+ const shouldAutoExpand = element.autoExpand || state.config.readonly;
914
+ if (!shouldAutoExpand) return;
915
+ if (element.multiple) {
916
+ const textareas = scopeRoot.querySelectorAll(
917
+ `textarea[name^="${fieldPath}["]`
918
+ );
919
+ textareas.forEach((textarea) => {
920
+ textarea.dispatchEvent(new Event("input"));
921
+ });
922
+ } else {
923
+ const textarea = scopeRoot.querySelector(
924
+ `textarea[name="${fieldPath}"]`
925
+ );
926
+ if (textarea) {
927
+ textarea.dispatchEvent(new Event("input"));
928
+ }
929
+ }
891
930
  }
892
931
 
893
932
  // src/components/number.ts
@@ -1546,6 +1585,421 @@ function updateSelectField(element, fieldPath, value, context) {
1546
1585
  }
1547
1586
  }
1548
1587
 
1588
+ // src/components/switcher.ts
1589
+ function applySelectedStyle(btn) {
1590
+ btn.style.backgroundColor = "var(--fb-primary-color)";
1591
+ btn.style.color = "#ffffff";
1592
+ btn.style.borderColor = "var(--fb-primary-color)";
1593
+ }
1594
+ function applyUnselectedStyle(btn) {
1595
+ btn.style.backgroundColor = "transparent";
1596
+ btn.style.color = "var(--fb-text-color)";
1597
+ btn.style.borderColor = "var(--fb-border-color)";
1598
+ }
1599
+ function buildSegmentedGroup(element, currentValue, hiddenInput, readonly, onChange) {
1600
+ const options = element.options || [];
1601
+ const group = document.createElement("div");
1602
+ group.className = "fb-switcher-group";
1603
+ group.style.cssText = `
1604
+ display: inline-flex;
1605
+ flex-direction: row;
1606
+ flex-wrap: nowrap;
1607
+ `;
1608
+ const buttons = [];
1609
+ options.forEach((option, index) => {
1610
+ const btn = document.createElement("button");
1611
+ btn.type = "button";
1612
+ btn.className = "fb-switcher-btn";
1613
+ btn.dataset.value = option.value;
1614
+ btn.textContent = option.label;
1615
+ btn.style.cssText = `
1616
+ padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
1617
+ font-size: var(--fb-font-size);
1618
+ border-width: var(--fb-border-width);
1619
+ border-style: solid;
1620
+ cursor: ${readonly ? "default" : "pointer"};
1621
+ transition: background-color var(--fb-transition-duration), color var(--fb-transition-duration), border-color var(--fb-transition-duration);
1622
+ white-space: nowrap;
1623
+ line-height: 1.25;
1624
+ outline: none;
1625
+ `;
1626
+ if (options.length === 1) {
1627
+ btn.style.borderRadius = "var(--fb-border-radius)";
1628
+ } else if (index === 0) {
1629
+ btn.style.borderRadius = "var(--fb-border-radius) 0 0 var(--fb-border-radius)";
1630
+ btn.style.borderRightWidth = "0";
1631
+ } else if (index === options.length - 1) {
1632
+ btn.style.borderRadius = "0 var(--fb-border-radius) var(--fb-border-radius) 0";
1633
+ } else {
1634
+ btn.style.borderRadius = "0";
1635
+ btn.style.borderRightWidth = "0";
1636
+ }
1637
+ if (option.value === currentValue) {
1638
+ applySelectedStyle(btn);
1639
+ } else {
1640
+ applyUnselectedStyle(btn);
1641
+ }
1642
+ if (!readonly) {
1643
+ btn.addEventListener("click", () => {
1644
+ hiddenInput.value = option.value;
1645
+ buttons.forEach((b) => {
1646
+ if (b.dataset.value === option.value) {
1647
+ applySelectedStyle(b);
1648
+ } else {
1649
+ applyUnselectedStyle(b);
1650
+ }
1651
+ });
1652
+ if (onChange) {
1653
+ onChange(option.value);
1654
+ }
1655
+ });
1656
+ btn.addEventListener("mouseenter", () => {
1657
+ if (hiddenInput.value !== option.value) {
1658
+ btn.style.backgroundColor = "var(--fb-background-hover-color)";
1659
+ }
1660
+ });
1661
+ btn.addEventListener("mouseleave", () => {
1662
+ if (hiddenInput.value !== option.value) {
1663
+ btn.style.backgroundColor = "transparent";
1664
+ }
1665
+ });
1666
+ }
1667
+ buttons.push(btn);
1668
+ group.appendChild(btn);
1669
+ });
1670
+ return group;
1671
+ }
1672
+ function renderSwitcherElement(element, ctx, wrapper, pathKey) {
1673
+ const state = ctx.state;
1674
+ const initialValue = String(ctx.prefill[element.key] ?? element.default ?? "");
1675
+ const hiddenInput = document.createElement("input");
1676
+ hiddenInput.type = "hidden";
1677
+ hiddenInput.name = pathKey;
1678
+ hiddenInput.value = initialValue;
1679
+ const readonly = state.config.readonly;
1680
+ const onChange = !readonly && ctx.instance ? (value) => {
1681
+ ctx.instance.triggerOnChange(pathKey, value);
1682
+ } : null;
1683
+ const group = buildSegmentedGroup(
1684
+ element,
1685
+ initialValue,
1686
+ hiddenInput,
1687
+ readonly,
1688
+ onChange
1689
+ );
1690
+ wrapper.appendChild(hiddenInput);
1691
+ wrapper.appendChild(group);
1692
+ if (!readonly) {
1693
+ const hint = document.createElement("p");
1694
+ hint.className = "text-xs text-gray-500 mt-1";
1695
+ hint.textContent = makeFieldHint(element, state);
1696
+ wrapper.appendChild(hint);
1697
+ }
1698
+ }
1699
+ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
1700
+ const state = ctx.state;
1701
+ const prefillValues = ctx.prefill[element.key] || [];
1702
+ const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
1703
+ const minCount = element.minCount ?? 1;
1704
+ const maxCount = element.maxCount ?? Infinity;
1705
+ while (values.length < minCount) {
1706
+ values.push(element.default || element.options?.[0]?.value || "");
1707
+ }
1708
+ const readonly = state.config.readonly;
1709
+ const container = document.createElement("div");
1710
+ container.className = "space-y-2";
1711
+ wrapper.appendChild(container);
1712
+ function updateIndices() {
1713
+ const items = container.querySelectorAll(".multiple-switcher-item");
1714
+ items.forEach((item, index) => {
1715
+ const input = item.querySelector("input[type=hidden]");
1716
+ if (input) {
1717
+ input.name = `${pathKey}[${index}]`;
1718
+ }
1719
+ });
1720
+ }
1721
+ function addSwitcherItem(value = "", index = -1) {
1722
+ const currentIndex = index === -1 ? container.children.length : index;
1723
+ const itemPathKey = `${pathKey}[${currentIndex}]`;
1724
+ const itemWrapper = document.createElement("div");
1725
+ itemWrapper.className = "multiple-switcher-item flex items-center gap-2";
1726
+ const hiddenInput = document.createElement("input");
1727
+ hiddenInput.type = "hidden";
1728
+ hiddenInput.name = itemPathKey;
1729
+ hiddenInput.value = value;
1730
+ itemWrapper.appendChild(hiddenInput);
1731
+ const onChange = !readonly && ctx.instance ? (val) => {
1732
+ ctx.instance.triggerOnChange(hiddenInput.name, val);
1733
+ } : null;
1734
+ const group = buildSegmentedGroup(
1735
+ element,
1736
+ value,
1737
+ hiddenInput,
1738
+ readonly,
1739
+ onChange
1740
+ );
1741
+ itemWrapper.appendChild(group);
1742
+ if (index === -1) {
1743
+ container.appendChild(itemWrapper);
1744
+ } else {
1745
+ container.insertBefore(itemWrapper, container.children[index]);
1746
+ }
1747
+ updateIndices();
1748
+ return itemWrapper;
1749
+ }
1750
+ function updateRemoveButtons() {
1751
+ if (readonly) return;
1752
+ const items = container.querySelectorAll(".multiple-switcher-item");
1753
+ const currentCount = items.length;
1754
+ items.forEach((item) => {
1755
+ let removeBtn = item.querySelector(
1756
+ ".remove-item-btn"
1757
+ );
1758
+ if (!removeBtn) {
1759
+ removeBtn = document.createElement("button");
1760
+ removeBtn.type = "button";
1761
+ removeBtn.className = "remove-item-btn px-2 py-1 rounded";
1762
+ removeBtn.style.cssText = `
1763
+ color: var(--fb-error-color);
1764
+ background-color: transparent;
1765
+ transition: background-color var(--fb-transition-duration);
1766
+ `;
1767
+ removeBtn.innerHTML = "\u2715";
1768
+ removeBtn.addEventListener("mouseenter", () => {
1769
+ removeBtn.style.backgroundColor = "var(--fb-background-hover-color)";
1770
+ });
1771
+ removeBtn.addEventListener("mouseleave", () => {
1772
+ removeBtn.style.backgroundColor = "transparent";
1773
+ });
1774
+ removeBtn.onclick = () => {
1775
+ const currentIndex = Array.from(container.children).indexOf(
1776
+ item
1777
+ );
1778
+ if (container.children.length > minCount) {
1779
+ values.splice(currentIndex, 1);
1780
+ item.remove();
1781
+ updateIndices();
1782
+ updateAddButton();
1783
+ updateRemoveButtons();
1784
+ }
1785
+ };
1786
+ item.appendChild(removeBtn);
1787
+ }
1788
+ const disabled = currentCount <= minCount;
1789
+ removeBtn.disabled = disabled;
1790
+ removeBtn.style.opacity = disabled ? "0.5" : "1";
1791
+ removeBtn.style.pointerEvents = disabled ? "none" : "auto";
1792
+ });
1793
+ }
1794
+ let addRow = null;
1795
+ let countDisplay = null;
1796
+ if (!readonly) {
1797
+ addRow = document.createElement("div");
1798
+ addRow.className = "flex items-center gap-3 mt-2";
1799
+ const addBtn = document.createElement("button");
1800
+ addBtn.type = "button";
1801
+ addBtn.className = "add-switcher-btn px-3 py-1 rounded";
1802
+ addBtn.style.cssText = `
1803
+ color: var(--fb-primary-color);
1804
+ border: var(--fb-border-width) solid var(--fb-primary-color);
1805
+ background-color: transparent;
1806
+ font-size: var(--fb-font-size);
1807
+ transition: all var(--fb-transition-duration);
1808
+ `;
1809
+ addBtn.textContent = "+";
1810
+ addBtn.addEventListener("mouseenter", () => {
1811
+ addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
1812
+ });
1813
+ addBtn.addEventListener("mouseleave", () => {
1814
+ addBtn.style.backgroundColor = "transparent";
1815
+ });
1816
+ addBtn.onclick = () => {
1817
+ const defaultValue = element.default || element.options?.[0]?.value || "";
1818
+ values.push(defaultValue);
1819
+ addSwitcherItem(defaultValue);
1820
+ updateAddButton();
1821
+ updateRemoveButtons();
1822
+ };
1823
+ countDisplay = document.createElement("span");
1824
+ countDisplay.className = "text-sm text-gray-500";
1825
+ addRow.appendChild(addBtn);
1826
+ addRow.appendChild(countDisplay);
1827
+ wrapper.appendChild(addRow);
1828
+ }
1829
+ function updateAddButton() {
1830
+ if (!addRow || !countDisplay) return;
1831
+ const addBtn = addRow.querySelector(
1832
+ ".add-switcher-btn"
1833
+ );
1834
+ if (addBtn) {
1835
+ const disabled = values.length >= maxCount;
1836
+ addBtn.disabled = disabled;
1837
+ addBtn.style.opacity = disabled ? "0.5" : "1";
1838
+ addBtn.style.pointerEvents = disabled ? "none" : "auto";
1839
+ }
1840
+ countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
1841
+ }
1842
+ values.forEach((value) => addSwitcherItem(value));
1843
+ updateAddButton();
1844
+ updateRemoveButtons();
1845
+ if (!readonly) {
1846
+ const hint = document.createElement("p");
1847
+ hint.className = "text-xs text-gray-500 mt-1";
1848
+ hint.textContent = makeFieldHint(element, state);
1849
+ wrapper.appendChild(hint);
1850
+ }
1851
+ }
1852
+ function validateSwitcherElement(element, key, context) {
1853
+ const errors = [];
1854
+ const { scopeRoot, skipValidation } = context;
1855
+ const markValidity = (input, errorMessage) => {
1856
+ if (!input) return;
1857
+ const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
1858
+ let errorElement = document.getElementById(errorId);
1859
+ if (errorMessage) {
1860
+ input.classList.add("invalid");
1861
+ input.title = errorMessage;
1862
+ if (!errorElement) {
1863
+ errorElement = document.createElement("div");
1864
+ errorElement.id = errorId;
1865
+ errorElement.className = "error-message";
1866
+ errorElement.style.cssText = `
1867
+ color: var(--fb-error-color);
1868
+ font-size: var(--fb-font-size-small);
1869
+ margin-top: 0.25rem;
1870
+ `;
1871
+ if (input.nextSibling) {
1872
+ input.parentNode?.insertBefore(errorElement, input.nextSibling);
1873
+ } else {
1874
+ input.parentNode?.appendChild(errorElement);
1875
+ }
1876
+ }
1877
+ errorElement.textContent = errorMessage;
1878
+ errorElement.style.display = "block";
1879
+ } else {
1880
+ input.classList.remove("invalid");
1881
+ input.title = "";
1882
+ if (errorElement) {
1883
+ errorElement.remove();
1884
+ }
1885
+ }
1886
+ };
1887
+ const validateMultipleCount = (fieldKey, values, el, filterFn) => {
1888
+ if (skipValidation) return;
1889
+ const { state } = context;
1890
+ const filteredValues = values.filter(filterFn);
1891
+ const minCount = "minCount" in el ? el.minCount ?? 1 : 1;
1892
+ const maxCount = "maxCount" in el ? el.maxCount ?? Infinity : Infinity;
1893
+ if (el.required && filteredValues.length === 0) {
1894
+ errors.push(`${fieldKey}: ${t("required", state)}`);
1895
+ }
1896
+ if (filteredValues.length < minCount) {
1897
+ errors.push(`${fieldKey}: ${t("minItems", state, { min: minCount })}`);
1898
+ }
1899
+ if (filteredValues.length > maxCount) {
1900
+ errors.push(`${fieldKey}: ${t("maxItems", state, { max: maxCount })}`);
1901
+ }
1902
+ };
1903
+ const validOptionValues = new Set(
1904
+ "options" in element ? element.options.map((o) => o.value) : []
1905
+ );
1906
+ if ("multiple" in element && element.multiple) {
1907
+ const inputs = scopeRoot.querySelectorAll(
1908
+ `input[type="hidden"][name^="${key}["]`
1909
+ );
1910
+ const values = [];
1911
+ inputs.forEach((input) => {
1912
+ const val = input?.value ?? "";
1913
+ values.push(val);
1914
+ if (!skipValidation && val !== "" && !validOptionValues.has(val)) {
1915
+ const msg = t("invalidOption", context.state);
1916
+ markValidity(input, msg);
1917
+ errors.push(`${key}: ${msg}`);
1918
+ } else {
1919
+ markValidity(input, null);
1920
+ }
1921
+ });
1922
+ validateMultipleCount(key, values, element, (v) => v !== "");
1923
+ return { value: values, errors };
1924
+ } else {
1925
+ const input = scopeRoot.querySelector(
1926
+ `input[type="hidden"][name$="${key}"]`
1927
+ );
1928
+ const val = input?.value ?? "";
1929
+ if (!skipValidation && element.required && val === "") {
1930
+ const msg = t("required", context.state);
1931
+ errors.push(`${key}: ${msg}`);
1932
+ markValidity(input, msg);
1933
+ return { value: null, errors };
1934
+ }
1935
+ if (!skipValidation && val !== "" && !validOptionValues.has(val)) {
1936
+ const msg = t("invalidOption", context.state);
1937
+ errors.push(`${key}: ${msg}`);
1938
+ markValidity(input, msg);
1939
+ return { value: null, errors };
1940
+ }
1941
+ markValidity(input, null);
1942
+ return { value: val === "" ? null : val, errors };
1943
+ }
1944
+ }
1945
+ function updateSwitcherField(element, fieldPath, value, context) {
1946
+ const { scopeRoot } = context;
1947
+ if ("multiple" in element && element.multiple) {
1948
+ if (!Array.isArray(value)) {
1949
+ console.warn(
1950
+ `updateSwitcherField: Expected array for multiple field "${fieldPath}", got ${typeof value}`
1951
+ );
1952
+ return;
1953
+ }
1954
+ const inputs = scopeRoot.querySelectorAll(
1955
+ `input[type="hidden"][name^="${fieldPath}["]`
1956
+ );
1957
+ inputs.forEach((input, index) => {
1958
+ if (index < value.length) {
1959
+ const newVal = value[index] != null ? String(value[index]) : "";
1960
+ input.value = newVal;
1961
+ const group = input.parentElement?.querySelector(".fb-switcher-group");
1962
+ if (group) {
1963
+ group.querySelectorAll(".fb-switcher-btn").forEach((btn) => {
1964
+ if (btn.dataset.value === newVal) {
1965
+ applySelectedStyle(btn);
1966
+ } else {
1967
+ applyUnselectedStyle(btn);
1968
+ }
1969
+ });
1970
+ }
1971
+ input.classList.remove("invalid");
1972
+ input.title = "";
1973
+ }
1974
+ });
1975
+ if (value.length !== inputs.length) {
1976
+ console.warn(
1977
+ `updateSwitcherField: Multiple field "${fieldPath}" has ${inputs.length} inputs but received ${value.length} values. Consider re-rendering for add/remove.`
1978
+ );
1979
+ }
1980
+ } else {
1981
+ const input = scopeRoot.querySelector(
1982
+ `input[type="hidden"][name="${fieldPath}"]`
1983
+ );
1984
+ if (input) {
1985
+ const newVal = value != null ? String(value) : "";
1986
+ input.value = newVal;
1987
+ const group = input.parentElement?.querySelector(".fb-switcher-group");
1988
+ if (group) {
1989
+ group.querySelectorAll(".fb-switcher-btn").forEach((btn) => {
1990
+ if (btn.dataset.value === newVal) {
1991
+ applySelectedStyle(btn);
1992
+ } else {
1993
+ applyUnselectedStyle(btn);
1994
+ }
1995
+ });
1996
+ }
1997
+ input.classList.remove("invalid");
1998
+ input.title = "";
1999
+ }
2000
+ }
2001
+ }
2002
+
1549
2003
  // src/components/file.ts
1550
2004
  function renderLocalImagePreview(container, file, fileName, state) {
1551
2005
  const img = document.createElement("img");
@@ -3877,10 +4331,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3877
4331
  rem.addEventListener("mouseleave", () => {
3878
4332
  rem.style.backgroundColor = "transparent";
3879
4333
  });
3880
- rem.onclick = () => {
3881
- item.remove();
3882
- updateAddButton();
3883
- };
4334
+ rem.onclick = () => handleRemoveItem(item);
3884
4335
  item.style.position = "relative";
3885
4336
  item.appendChild(rem);
3886
4337
  }
@@ -3902,6 +4353,10 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3902
4353
  }
3903
4354
  countDisplay.textContent = `${currentCount}/${max === Infinity ? "\u221E" : max}`;
3904
4355
  };
4356
+ const handleRemoveItem = (item) => {
4357
+ item.remove();
4358
+ updateAddButton();
4359
+ };
3905
4360
  if (pre && Array.isArray(pre)) {
3906
4361
  pre.forEach((prefillObj, idx) => {
3907
4362
  const mergedPrefill = mergeWithDefaults(prefillObj || {}, childDefaults);
@@ -3945,10 +4400,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3945
4400
  rem.addEventListener("mouseleave", () => {
3946
4401
  rem.style.backgroundColor = "transparent";
3947
4402
  });
3948
- rem.onclick = () => {
3949
- item.remove();
3950
- updateAddButton();
3951
- };
4403
+ rem.onclick = () => handleRemoveItem(item);
3952
4404
  item.style.position = "relative";
3953
4405
  item.appendChild(rem);
3954
4406
  }
@@ -3999,8 +4451,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
3999
4451
  });
4000
4452
  rem.onclick = () => {
4001
4453
  if (countItems() > min) {
4002
- item.remove();
4003
- updateAddButton();
4454
+ handleRemoveItem(item);
4004
4455
  }
4005
4456
  };
4006
4457
  item.style.position = "relative";
@@ -4058,15 +4509,16 @@ function validateContainerElement(element, key, context) {
4058
4509
  "[data-container-item]"
4059
4510
  );
4060
4511
  const containerWrappers = Array.from(allContainerWrappers).filter((el) => {
4061
- const attr = el.getAttribute("data-container-item");
4062
- return attr?.startsWith(`${key}[`);
4512
+ const attr = el.getAttribute("data-container-item") || "";
4513
+ if (!attr.startsWith(`${key}[`)) return false;
4514
+ const suffix = attr.slice(key.length);
4515
+ return /^\[\d+\]$/.test(suffix);
4063
4516
  });
4064
- const itemCount = containerWrappers.length;
4065
- for (let i = 0; i < itemCount; i++) {
4517
+ containerWrappers.forEach((itemContainer) => {
4066
4518
  const itemData = {};
4067
- const itemContainer = scopeRoot.querySelector(
4068
- `[data-container-item="${key}[${i}]"]`
4069
- ) || scopeRoot;
4519
+ const containerAttr = itemContainer.getAttribute("data-container-item") || "";
4520
+ const indexMatch = containerAttr.match(/\[(\d+)\]$/);
4521
+ const domIndex = indexMatch ? parseInt(indexMatch[1], 10) : 0;
4070
4522
  element.elements.forEach((child) => {
4071
4523
  if (child.enableIf) {
4072
4524
  try {
@@ -4083,7 +4535,7 @@ function validateContainerElement(element, key, context) {
4083
4535
  }
4084
4536
  } catch (error) {
4085
4537
  console.error(
4086
- `Error evaluating enableIf for field "${child.key}" in container "${key}[${i}]":`,
4538
+ `Error evaluating enableIf for field "${child.key}" in container "${key}[${domIndex}]":`,
4087
4539
  error
4088
4540
  );
4089
4541
  }
@@ -4091,7 +4543,7 @@ function validateContainerElement(element, key, context) {
4091
4543
  if (child.hidden || child.type === "hidden") {
4092
4544
  itemData[child.key] = child.default !== void 0 ? child.default : null;
4093
4545
  } else {
4094
- const childKey = `${key}[${i}].${child.key}`;
4546
+ const childKey = `${key}[${domIndex}].${child.key}`;
4095
4547
  itemData[child.key] = validateElement(
4096
4548
  { ...child, key: childKey },
4097
4549
  { path },
@@ -4100,7 +4552,7 @@ function validateContainerElement(element, key, context) {
4100
4552
  }
4101
4553
  });
4102
4554
  items.push(itemData);
4103
- }
4555
+ });
4104
4556
  validateContainerCount(key, items, element);
4105
4557
  return { value: items, errors };
4106
4558
  } else {
@@ -4560,6 +5012,13 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
4560
5012
  renderSelectElement(element, ctx, wrapper, pathKey);
4561
5013
  }
4562
5014
  break;
5015
+ case "switcher":
5016
+ if (isMultiple) {
5017
+ renderMultipleSwitcherElement(element, ctx, wrapper, pathKey);
5018
+ } else {
5019
+ renderSwitcherElement(element, ctx, wrapper, pathKey);
5020
+ }
5021
+ break;
4563
5022
  case "file":
4564
5023
  if (isMultiple) {
4565
5024
  renderMultipleFileElement(element, ctx, wrapper, pathKey);
@@ -4683,7 +5142,8 @@ var defaultConfig = {
4683
5142
  invalidHexColour: "Invalid hex color",
4684
5143
  minFiles: "Minimum {min} files required",
4685
5144
  maxFiles: "Maximum {max} files allowed",
4686
- unsupportedFieldType: "Unsupported field type: {type}"
5145
+ unsupportedFieldType: "Unsupported field type: {type}",
5146
+ invalidOption: "Invalid option"
4687
5147
  },
4688
5148
  ru: {
4689
5149
  // UI texts
@@ -4728,7 +5188,8 @@ var defaultConfig = {
4728
5188
  invalidHexColour: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u0446\u0432\u0435\u0442\u0430",
4729
5189
  minFiles: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u0444\u0430\u0439\u043B\u043E\u0432",
4730
5190
  maxFiles: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0444\u0430\u0439\u043B\u043E\u0432",
4731
- unsupportedFieldType: "\u041D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0442\u0438\u043F \u043F\u043E\u043B\u044F: {type}"
5191
+ unsupportedFieldType: "\u041D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0442\u0438\u043F \u043F\u043E\u043B\u044F: {type}",
5192
+ invalidOption: "\u041D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435"
4732
5193
  }
4733
5194
  },
4734
5195
  theme: {}
@@ -4973,6 +5434,10 @@ var componentRegistry = {
4973
5434
  validate: validateSelectElement,
4974
5435
  update: updateSelectField
4975
5436
  },
5437
+ switcher: {
5438
+ validate: validateSwitcherElement,
5439
+ update: updateSwitcherField
5440
+ },
4976
5441
  file: {
4977
5442
  validate: validateFileElement,
4978
5443
  update: updateFileField
@@ -5751,8 +6216,18 @@ var FormBuilderInstance = class {
5751
6216
  if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
5752
6217
  const containerData = formData?.[element.key];
5753
6218
  if (Array.isArray(containerData)) {
5754
- containerData.forEach((_, index) => {
5755
- checkElements(element.elements, `${fullPath}[${index}]`);
6219
+ const containerItems = this.state.formRoot.querySelectorAll(
6220
+ `[data-container-item]`
6221
+ );
6222
+ const directItems = Array.from(containerItems).filter((el) => {
6223
+ const attr = el.getAttribute("data-container-item") || "";
6224
+ if (!attr.startsWith(`${fullPath}[`)) return false;
6225
+ const suffix = attr.slice(fullPath.length);
6226
+ return /^\[\d+\]$/.test(suffix);
6227
+ });
6228
+ directItems.forEach((el) => {
6229
+ const attr = el.getAttribute("data-container-item") || "";
6230
+ checkElements(element.elements, attr);
5756
6231
  });
5757
6232
  } else {
5758
6233
  checkElements(element.elements, fullPath);