@dmitryvim/form-builder 0.2.17 → 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");
@@ -4558,6 +5012,13 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
4558
5012
  renderSelectElement(element, ctx, wrapper, pathKey);
4559
5013
  }
4560
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;
4561
5022
  case "file":
4562
5023
  if (isMultiple) {
4563
5024
  renderMultipleFileElement(element, ctx, wrapper, pathKey);
@@ -4681,7 +5142,8 @@ var defaultConfig = {
4681
5142
  invalidHexColour: "Invalid hex color",
4682
5143
  minFiles: "Minimum {min} files required",
4683
5144
  maxFiles: "Maximum {max} files allowed",
4684
- unsupportedFieldType: "Unsupported field type: {type}"
5145
+ unsupportedFieldType: "Unsupported field type: {type}",
5146
+ invalidOption: "Invalid option"
4685
5147
  },
4686
5148
  ru: {
4687
5149
  // UI texts
@@ -4726,7 +5188,8 @@ var defaultConfig = {
4726
5188
  invalidHexColour: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u0446\u0432\u0435\u0442\u0430",
4727
5189
  minFiles: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u0444\u0430\u0439\u043B\u043E\u0432",
4728
5190
  maxFiles: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0444\u0430\u0439\u043B\u043E\u0432",
4729
- 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"
4730
5193
  }
4731
5194
  },
4732
5195
  theme: {}
@@ -4971,6 +5434,10 @@ var componentRegistry = {
4971
5434
  validate: validateSelectElement,
4972
5435
  update: updateSelectField
4973
5436
  },
5437
+ switcher: {
5438
+ validate: validateSwitcherElement,
5439
+ update: updateSwitcherField
5440
+ },
4974
5441
  file: {
4975
5442
  validate: validateFileElement,
4976
5443
  update: updateFileField