@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/README.md +1 -0
- package/dist/browser/formbuilder.min.js +99 -71
- package/dist/browser/formbuilder.v0.2.18.min.js +386 -0
- package/dist/cjs/index.cjs +478 -2
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +469 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +99 -71
- package/dist/types/components/index.d.ts +2 -1
- package/dist/types/components/switcher.d.ts +13 -0
- package/dist/types/components/textarea.d.ts +2 -1
- package/dist/types/types/config.d.ts +1 -0
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/schema.d.ts +9 -1
- package/package.json +2 -2
- package/dist/browser/formbuilder.v0.2.17.min.js +0 -358
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
|