@dmitryvim/form-builder 0.2.17 → 0.2.19
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 +58 -0
- package/dist/browser/formbuilder.min.js +168 -78
- package/dist/browser/formbuilder.v0.2.19.min.js +448 -0
- package/dist/cjs/index.cjs +1530 -14
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +1491 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +168 -78
- package/dist/types/components/index.d.ts +3 -1
- package/dist/types/components/switcher.d.ts +13 -0
- package/dist/types/components/table.d.ts +4 -0
- package/dist/types/components/textarea.d.ts +2 -1
- package/dist/types/types/config.d.ts +7 -0
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/schema.d.ts +36 -1
- package/package.json +2 -2
- package/dist/browser/formbuilder.v0.2.17.min.js +0 -358
package/dist/cjs/index.cjs
CHANGED
|
@@ -729,6 +729,21 @@ function updateTextField(element, fieldPath, value, context) {
|
|
|
729
729
|
}
|
|
730
730
|
|
|
731
731
|
// src/components/textarea.ts
|
|
732
|
+
function applyAutoExpand(textarea) {
|
|
733
|
+
textarea.style.overflow = "hidden";
|
|
734
|
+
textarea.style.resize = "none";
|
|
735
|
+
const lineCount = (textarea.value.match(/\n/g) || []).length + 1;
|
|
736
|
+
textarea.rows = Math.max(1, lineCount);
|
|
737
|
+
const resize = () => {
|
|
738
|
+
if (!textarea.isConnected) return;
|
|
739
|
+
textarea.style.height = "0";
|
|
740
|
+
textarea.style.height = `${textarea.scrollHeight}px`;
|
|
741
|
+
};
|
|
742
|
+
textarea.addEventListener("input", resize);
|
|
743
|
+
setTimeout(() => {
|
|
744
|
+
if (textarea.isConnected) resize();
|
|
745
|
+
}, 0);
|
|
746
|
+
}
|
|
732
747
|
function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
733
748
|
const state = ctx.state;
|
|
734
749
|
const textareaWrapper = document.createElement("div");
|
|
@@ -749,6 +764,9 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
749
764
|
textareaInput.addEventListener("blur", handleChange);
|
|
750
765
|
textareaInput.addEventListener("input", handleChange);
|
|
751
766
|
}
|
|
767
|
+
if (element.autoExpand || state.config.readonly) {
|
|
768
|
+
applyAutoExpand(textareaInput);
|
|
769
|
+
}
|
|
752
770
|
textareaWrapper.appendChild(textareaInput);
|
|
753
771
|
if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
|
|
754
772
|
const counter = createCharCounter(element, textareaInput, true);
|
|
@@ -798,6 +816,9 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
798
816
|
textareaInput.addEventListener("blur", handleChange);
|
|
799
817
|
textareaInput.addEventListener("input", handleChange);
|
|
800
818
|
}
|
|
819
|
+
if (element.autoExpand || state.config.readonly) {
|
|
820
|
+
applyAutoExpand(textareaInput);
|
|
821
|
+
}
|
|
801
822
|
textareaContainer.appendChild(textareaInput);
|
|
802
823
|
if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
|
|
803
824
|
const counter = createCharCounter(element, textareaInput, true);
|
|
@@ -899,6 +920,24 @@ function validateTextareaElement(element, key, context) {
|
|
|
899
920
|
}
|
|
900
921
|
function updateTextareaField(element, fieldPath, value, context) {
|
|
901
922
|
updateTextField(element, fieldPath, value, context);
|
|
923
|
+
const { scopeRoot, state } = context;
|
|
924
|
+
const shouldAutoExpand = element.autoExpand || state.config.readonly;
|
|
925
|
+
if (!shouldAutoExpand) return;
|
|
926
|
+
if (element.multiple) {
|
|
927
|
+
const textareas = scopeRoot.querySelectorAll(
|
|
928
|
+
`textarea[name^="${fieldPath}["]`
|
|
929
|
+
);
|
|
930
|
+
textareas.forEach((textarea) => {
|
|
931
|
+
textarea.dispatchEvent(new Event("input"));
|
|
932
|
+
});
|
|
933
|
+
} else {
|
|
934
|
+
const textarea = scopeRoot.querySelector(
|
|
935
|
+
`textarea[name="${fieldPath}"]`
|
|
936
|
+
);
|
|
937
|
+
if (textarea) {
|
|
938
|
+
textarea.dispatchEvent(new Event("input"));
|
|
939
|
+
}
|
|
940
|
+
}
|
|
902
941
|
}
|
|
903
942
|
|
|
904
943
|
// src/components/number.ts
|
|
@@ -1546,23 +1585,447 @@ function updateSelectField(element, fieldPath, value, context) {
|
|
|
1546
1585
|
select.title = "";
|
|
1547
1586
|
}
|
|
1548
1587
|
});
|
|
1549
|
-
if (value.length !== selects.length) {
|
|
1588
|
+
if (value.length !== selects.length) {
|
|
1589
|
+
console.warn(
|
|
1590
|
+
`updateSelectField: Multiple field "${fieldPath}" has ${selects.length} selects but received ${value.length} values. Consider re-rendering for add/remove.`
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
} else {
|
|
1594
|
+
const select = scopeRoot.querySelector(
|
|
1595
|
+
`[name="${fieldPath}"]`
|
|
1596
|
+
);
|
|
1597
|
+
if (select) {
|
|
1598
|
+
select.value = value != null ? String(value) : "";
|
|
1599
|
+
const options = select.querySelectorAll("option");
|
|
1600
|
+
options.forEach((option) => {
|
|
1601
|
+
option.selected = option.value === String(value);
|
|
1602
|
+
});
|
|
1603
|
+
select.classList.remove("invalid");
|
|
1604
|
+
select.title = "";
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
// src/components/switcher.ts
|
|
1610
|
+
function applySelectedStyle(btn) {
|
|
1611
|
+
btn.style.backgroundColor = "var(--fb-primary-color)";
|
|
1612
|
+
btn.style.color = "#ffffff";
|
|
1613
|
+
btn.style.borderColor = "var(--fb-primary-color)";
|
|
1614
|
+
}
|
|
1615
|
+
function applyUnselectedStyle(btn) {
|
|
1616
|
+
btn.style.backgroundColor = "transparent";
|
|
1617
|
+
btn.style.color = "var(--fb-text-color)";
|
|
1618
|
+
btn.style.borderColor = "var(--fb-border-color)";
|
|
1619
|
+
}
|
|
1620
|
+
function buildSegmentedGroup(element, currentValue, hiddenInput, readonly, onChange) {
|
|
1621
|
+
const options = element.options || [];
|
|
1622
|
+
const group = document.createElement("div");
|
|
1623
|
+
group.className = "fb-switcher-group";
|
|
1624
|
+
group.style.cssText = `
|
|
1625
|
+
display: inline-flex;
|
|
1626
|
+
flex-direction: row;
|
|
1627
|
+
flex-wrap: nowrap;
|
|
1628
|
+
`;
|
|
1629
|
+
const buttons = [];
|
|
1630
|
+
options.forEach((option, index) => {
|
|
1631
|
+
const btn = document.createElement("button");
|
|
1632
|
+
btn.type = "button";
|
|
1633
|
+
btn.className = "fb-switcher-btn";
|
|
1634
|
+
btn.dataset.value = option.value;
|
|
1635
|
+
btn.textContent = option.label;
|
|
1636
|
+
btn.style.cssText = `
|
|
1637
|
+
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
1638
|
+
font-size: var(--fb-font-size);
|
|
1639
|
+
border-width: var(--fb-border-width);
|
|
1640
|
+
border-style: solid;
|
|
1641
|
+
cursor: ${readonly ? "default" : "pointer"};
|
|
1642
|
+
transition: background-color var(--fb-transition-duration), color var(--fb-transition-duration), border-color var(--fb-transition-duration);
|
|
1643
|
+
white-space: nowrap;
|
|
1644
|
+
line-height: 1.25;
|
|
1645
|
+
outline: none;
|
|
1646
|
+
`;
|
|
1647
|
+
if (options.length === 1) {
|
|
1648
|
+
btn.style.borderRadius = "var(--fb-border-radius)";
|
|
1649
|
+
} else if (index === 0) {
|
|
1650
|
+
btn.style.borderRadius = "var(--fb-border-radius) 0 0 var(--fb-border-radius)";
|
|
1651
|
+
btn.style.borderRightWidth = "0";
|
|
1652
|
+
} else if (index === options.length - 1) {
|
|
1653
|
+
btn.style.borderRadius = "0 var(--fb-border-radius) var(--fb-border-radius) 0";
|
|
1654
|
+
} else {
|
|
1655
|
+
btn.style.borderRadius = "0";
|
|
1656
|
+
btn.style.borderRightWidth = "0";
|
|
1657
|
+
}
|
|
1658
|
+
if (option.value === currentValue) {
|
|
1659
|
+
applySelectedStyle(btn);
|
|
1660
|
+
} else {
|
|
1661
|
+
applyUnselectedStyle(btn);
|
|
1662
|
+
}
|
|
1663
|
+
if (!readonly) {
|
|
1664
|
+
btn.addEventListener("click", () => {
|
|
1665
|
+
hiddenInput.value = option.value;
|
|
1666
|
+
buttons.forEach((b) => {
|
|
1667
|
+
if (b.dataset.value === option.value) {
|
|
1668
|
+
applySelectedStyle(b);
|
|
1669
|
+
} else {
|
|
1670
|
+
applyUnselectedStyle(b);
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1673
|
+
if (onChange) {
|
|
1674
|
+
onChange(option.value);
|
|
1675
|
+
}
|
|
1676
|
+
});
|
|
1677
|
+
btn.addEventListener("mouseenter", () => {
|
|
1678
|
+
if (hiddenInput.value !== option.value) {
|
|
1679
|
+
btn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
1680
|
+
}
|
|
1681
|
+
});
|
|
1682
|
+
btn.addEventListener("mouseleave", () => {
|
|
1683
|
+
if (hiddenInput.value !== option.value) {
|
|
1684
|
+
btn.style.backgroundColor = "transparent";
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
buttons.push(btn);
|
|
1689
|
+
group.appendChild(btn);
|
|
1690
|
+
});
|
|
1691
|
+
return group;
|
|
1692
|
+
}
|
|
1693
|
+
function renderSwitcherElement(element, ctx, wrapper, pathKey) {
|
|
1694
|
+
var _a, _b;
|
|
1695
|
+
const state = ctx.state;
|
|
1696
|
+
const initialValue = String((_b = (_a = ctx.prefill[element.key]) != null ? _a : element.default) != null ? _b : "");
|
|
1697
|
+
const hiddenInput = document.createElement("input");
|
|
1698
|
+
hiddenInput.type = "hidden";
|
|
1699
|
+
hiddenInput.name = pathKey;
|
|
1700
|
+
hiddenInput.value = initialValue;
|
|
1701
|
+
const readonly = state.config.readonly;
|
|
1702
|
+
const onChange = !readonly && ctx.instance ? (value) => {
|
|
1703
|
+
ctx.instance.triggerOnChange(pathKey, value);
|
|
1704
|
+
} : null;
|
|
1705
|
+
const group = buildSegmentedGroup(
|
|
1706
|
+
element,
|
|
1707
|
+
initialValue,
|
|
1708
|
+
hiddenInput,
|
|
1709
|
+
readonly,
|
|
1710
|
+
onChange
|
|
1711
|
+
);
|
|
1712
|
+
wrapper.appendChild(hiddenInput);
|
|
1713
|
+
wrapper.appendChild(group);
|
|
1714
|
+
if (!readonly) {
|
|
1715
|
+
const hint = document.createElement("p");
|
|
1716
|
+
hint.className = "text-xs text-gray-500 mt-1";
|
|
1717
|
+
hint.textContent = makeFieldHint(element, state);
|
|
1718
|
+
wrapper.appendChild(hint);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
|
|
1722
|
+
var _a, _b, _c, _d;
|
|
1723
|
+
const state = ctx.state;
|
|
1724
|
+
const prefillValues = ctx.prefill[element.key] || [];
|
|
1725
|
+
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
1726
|
+
const minCount = (_a = element.minCount) != null ? _a : 1;
|
|
1727
|
+
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
1728
|
+
while (values.length < minCount) {
|
|
1729
|
+
values.push(element.default || ((_d = (_c = element.options) == null ? void 0 : _c[0]) == null ? void 0 : _d.value) || "");
|
|
1730
|
+
}
|
|
1731
|
+
const readonly = state.config.readonly;
|
|
1732
|
+
const container = document.createElement("div");
|
|
1733
|
+
container.className = "space-y-2";
|
|
1734
|
+
wrapper.appendChild(container);
|
|
1735
|
+
function updateIndices() {
|
|
1736
|
+
const items = container.querySelectorAll(".multiple-switcher-item");
|
|
1737
|
+
items.forEach((item, index) => {
|
|
1738
|
+
const input = item.querySelector("input[type=hidden]");
|
|
1739
|
+
if (input) {
|
|
1740
|
+
input.name = `${pathKey}[${index}]`;
|
|
1741
|
+
}
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
function addSwitcherItem(value = "", index = -1) {
|
|
1745
|
+
const currentIndex = index === -1 ? container.children.length : index;
|
|
1746
|
+
const itemPathKey = `${pathKey}[${currentIndex}]`;
|
|
1747
|
+
const itemWrapper = document.createElement("div");
|
|
1748
|
+
itemWrapper.className = "multiple-switcher-item flex items-center gap-2";
|
|
1749
|
+
const hiddenInput = document.createElement("input");
|
|
1750
|
+
hiddenInput.type = "hidden";
|
|
1751
|
+
hiddenInput.name = itemPathKey;
|
|
1752
|
+
hiddenInput.value = value;
|
|
1753
|
+
itemWrapper.appendChild(hiddenInput);
|
|
1754
|
+
const onChange = !readonly && ctx.instance ? (val) => {
|
|
1755
|
+
ctx.instance.triggerOnChange(hiddenInput.name, val);
|
|
1756
|
+
} : null;
|
|
1757
|
+
const group = buildSegmentedGroup(
|
|
1758
|
+
element,
|
|
1759
|
+
value,
|
|
1760
|
+
hiddenInput,
|
|
1761
|
+
readonly,
|
|
1762
|
+
onChange
|
|
1763
|
+
);
|
|
1764
|
+
itemWrapper.appendChild(group);
|
|
1765
|
+
if (index === -1) {
|
|
1766
|
+
container.appendChild(itemWrapper);
|
|
1767
|
+
} else {
|
|
1768
|
+
container.insertBefore(itemWrapper, container.children[index]);
|
|
1769
|
+
}
|
|
1770
|
+
updateIndices();
|
|
1771
|
+
return itemWrapper;
|
|
1772
|
+
}
|
|
1773
|
+
function updateRemoveButtons() {
|
|
1774
|
+
if (readonly) return;
|
|
1775
|
+
const items = container.querySelectorAll(".multiple-switcher-item");
|
|
1776
|
+
const currentCount = items.length;
|
|
1777
|
+
items.forEach((item) => {
|
|
1778
|
+
let removeBtn = item.querySelector(
|
|
1779
|
+
".remove-item-btn"
|
|
1780
|
+
);
|
|
1781
|
+
if (!removeBtn) {
|
|
1782
|
+
removeBtn = document.createElement("button");
|
|
1783
|
+
removeBtn.type = "button";
|
|
1784
|
+
removeBtn.className = "remove-item-btn px-2 py-1 rounded";
|
|
1785
|
+
removeBtn.style.cssText = `
|
|
1786
|
+
color: var(--fb-error-color);
|
|
1787
|
+
background-color: transparent;
|
|
1788
|
+
transition: background-color var(--fb-transition-duration);
|
|
1789
|
+
`;
|
|
1790
|
+
removeBtn.innerHTML = "\u2715";
|
|
1791
|
+
removeBtn.addEventListener("mouseenter", () => {
|
|
1792
|
+
removeBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
1793
|
+
});
|
|
1794
|
+
removeBtn.addEventListener("mouseleave", () => {
|
|
1795
|
+
removeBtn.style.backgroundColor = "transparent";
|
|
1796
|
+
});
|
|
1797
|
+
removeBtn.onclick = () => {
|
|
1798
|
+
const currentIndex = Array.from(container.children).indexOf(
|
|
1799
|
+
item
|
|
1800
|
+
);
|
|
1801
|
+
if (container.children.length > minCount) {
|
|
1802
|
+
values.splice(currentIndex, 1);
|
|
1803
|
+
item.remove();
|
|
1804
|
+
updateIndices();
|
|
1805
|
+
updateAddButton();
|
|
1806
|
+
updateRemoveButtons();
|
|
1807
|
+
}
|
|
1808
|
+
};
|
|
1809
|
+
item.appendChild(removeBtn);
|
|
1810
|
+
}
|
|
1811
|
+
const disabled = currentCount <= minCount;
|
|
1812
|
+
removeBtn.disabled = disabled;
|
|
1813
|
+
removeBtn.style.opacity = disabled ? "0.5" : "1";
|
|
1814
|
+
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
let addRow = null;
|
|
1818
|
+
let countDisplay = null;
|
|
1819
|
+
if (!readonly) {
|
|
1820
|
+
addRow = document.createElement("div");
|
|
1821
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
1822
|
+
const addBtn = document.createElement("button");
|
|
1823
|
+
addBtn.type = "button";
|
|
1824
|
+
addBtn.className = "add-switcher-btn px-3 py-1 rounded";
|
|
1825
|
+
addBtn.style.cssText = `
|
|
1826
|
+
color: var(--fb-primary-color);
|
|
1827
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
1828
|
+
background-color: transparent;
|
|
1829
|
+
font-size: var(--fb-font-size);
|
|
1830
|
+
transition: all var(--fb-transition-duration);
|
|
1831
|
+
`;
|
|
1832
|
+
addBtn.textContent = "+";
|
|
1833
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
1834
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
1835
|
+
});
|
|
1836
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
1837
|
+
addBtn.style.backgroundColor = "transparent";
|
|
1838
|
+
});
|
|
1839
|
+
addBtn.onclick = () => {
|
|
1840
|
+
var _a2, _b2;
|
|
1841
|
+
const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
|
|
1842
|
+
values.push(defaultValue);
|
|
1843
|
+
addSwitcherItem(defaultValue);
|
|
1844
|
+
updateAddButton();
|
|
1845
|
+
updateRemoveButtons();
|
|
1846
|
+
};
|
|
1847
|
+
countDisplay = document.createElement("span");
|
|
1848
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
1849
|
+
addRow.appendChild(addBtn);
|
|
1850
|
+
addRow.appendChild(countDisplay);
|
|
1851
|
+
wrapper.appendChild(addRow);
|
|
1852
|
+
}
|
|
1853
|
+
function updateAddButton() {
|
|
1854
|
+
if (!addRow || !countDisplay) return;
|
|
1855
|
+
const addBtn = addRow.querySelector(
|
|
1856
|
+
".add-switcher-btn"
|
|
1857
|
+
);
|
|
1858
|
+
if (addBtn) {
|
|
1859
|
+
const disabled = values.length >= maxCount;
|
|
1860
|
+
addBtn.disabled = disabled;
|
|
1861
|
+
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
1862
|
+
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1863
|
+
}
|
|
1864
|
+
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
1865
|
+
}
|
|
1866
|
+
values.forEach((value) => addSwitcherItem(value));
|
|
1867
|
+
updateAddButton();
|
|
1868
|
+
updateRemoveButtons();
|
|
1869
|
+
if (!readonly) {
|
|
1870
|
+
const hint = document.createElement("p");
|
|
1871
|
+
hint.className = "text-xs text-gray-500 mt-1";
|
|
1872
|
+
hint.textContent = makeFieldHint(element, state);
|
|
1873
|
+
wrapper.appendChild(hint);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
function validateSwitcherElement(element, key, context) {
|
|
1877
|
+
var _a;
|
|
1878
|
+
const errors = [];
|
|
1879
|
+
const { scopeRoot, skipValidation } = context;
|
|
1880
|
+
const markValidity = (input, errorMessage) => {
|
|
1881
|
+
var _a2, _b;
|
|
1882
|
+
if (!input) return;
|
|
1883
|
+
const errorId = `error-${input.getAttribute("name") || Math.random().toString(36).substring(7)}`;
|
|
1884
|
+
let errorElement = document.getElementById(errorId);
|
|
1885
|
+
if (errorMessage) {
|
|
1886
|
+
input.classList.add("invalid");
|
|
1887
|
+
input.title = errorMessage;
|
|
1888
|
+
if (!errorElement) {
|
|
1889
|
+
errorElement = document.createElement("div");
|
|
1890
|
+
errorElement.id = errorId;
|
|
1891
|
+
errorElement.className = "error-message";
|
|
1892
|
+
errorElement.style.cssText = `
|
|
1893
|
+
color: var(--fb-error-color);
|
|
1894
|
+
font-size: var(--fb-font-size-small);
|
|
1895
|
+
margin-top: 0.25rem;
|
|
1896
|
+
`;
|
|
1897
|
+
if (input.nextSibling) {
|
|
1898
|
+
(_a2 = input.parentNode) == null ? void 0 : _a2.insertBefore(errorElement, input.nextSibling);
|
|
1899
|
+
} else {
|
|
1900
|
+
(_b = input.parentNode) == null ? void 0 : _b.appendChild(errorElement);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
errorElement.textContent = errorMessage;
|
|
1904
|
+
errorElement.style.display = "block";
|
|
1905
|
+
} else {
|
|
1906
|
+
input.classList.remove("invalid");
|
|
1907
|
+
input.title = "";
|
|
1908
|
+
if (errorElement) {
|
|
1909
|
+
errorElement.remove();
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
const validateMultipleCount = (fieldKey, values, el, filterFn) => {
|
|
1914
|
+
var _a2, _b;
|
|
1915
|
+
if (skipValidation) return;
|
|
1916
|
+
const { state } = context;
|
|
1917
|
+
const filteredValues = values.filter(filterFn);
|
|
1918
|
+
const minCount = "minCount" in el ? (_a2 = el.minCount) != null ? _a2 : 1 : 1;
|
|
1919
|
+
const maxCount = "maxCount" in el ? (_b = el.maxCount) != null ? _b : Infinity : Infinity;
|
|
1920
|
+
if (el.required && filteredValues.length === 0) {
|
|
1921
|
+
errors.push(`${fieldKey}: ${t("required", state)}`);
|
|
1922
|
+
}
|
|
1923
|
+
if (filteredValues.length < minCount) {
|
|
1924
|
+
errors.push(`${fieldKey}: ${t("minItems", state, { min: minCount })}`);
|
|
1925
|
+
}
|
|
1926
|
+
if (filteredValues.length > maxCount) {
|
|
1927
|
+
errors.push(`${fieldKey}: ${t("maxItems", state, { max: maxCount })}`);
|
|
1928
|
+
}
|
|
1929
|
+
};
|
|
1930
|
+
const validOptionValues = new Set(
|
|
1931
|
+
"options" in element ? element.options.map((o) => o.value) : []
|
|
1932
|
+
);
|
|
1933
|
+
if ("multiple" in element && element.multiple) {
|
|
1934
|
+
const inputs = scopeRoot.querySelectorAll(
|
|
1935
|
+
`input[type="hidden"][name^="${key}["]`
|
|
1936
|
+
);
|
|
1937
|
+
const values = [];
|
|
1938
|
+
inputs.forEach((input) => {
|
|
1939
|
+
var _a2;
|
|
1940
|
+
const val = (_a2 = input == null ? void 0 : input.value) != null ? _a2 : "";
|
|
1941
|
+
values.push(val);
|
|
1942
|
+
if (!skipValidation && val !== "" && !validOptionValues.has(val)) {
|
|
1943
|
+
const msg = t("invalidOption", context.state);
|
|
1944
|
+
markValidity(input, msg);
|
|
1945
|
+
errors.push(`${key}: ${msg}`);
|
|
1946
|
+
} else {
|
|
1947
|
+
markValidity(input, null);
|
|
1948
|
+
}
|
|
1949
|
+
});
|
|
1950
|
+
validateMultipleCount(key, values, element, (v) => v !== "");
|
|
1951
|
+
return { value: values, errors };
|
|
1952
|
+
} else {
|
|
1953
|
+
const input = scopeRoot.querySelector(
|
|
1954
|
+
`input[type="hidden"][name$="${key}"]`
|
|
1955
|
+
);
|
|
1956
|
+
const val = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
1957
|
+
if (!skipValidation && element.required && val === "") {
|
|
1958
|
+
const msg = t("required", context.state);
|
|
1959
|
+
errors.push(`${key}: ${msg}`);
|
|
1960
|
+
markValidity(input, msg);
|
|
1961
|
+
return { value: null, errors };
|
|
1962
|
+
}
|
|
1963
|
+
if (!skipValidation && val !== "" && !validOptionValues.has(val)) {
|
|
1964
|
+
const msg = t("invalidOption", context.state);
|
|
1965
|
+
errors.push(`${key}: ${msg}`);
|
|
1966
|
+
markValidity(input, msg);
|
|
1967
|
+
return { value: null, errors };
|
|
1968
|
+
}
|
|
1969
|
+
markValidity(input, null);
|
|
1970
|
+
return { value: val === "" ? null : val, errors };
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
function updateSwitcherField(element, fieldPath, value, context) {
|
|
1974
|
+
var _a;
|
|
1975
|
+
const { scopeRoot } = context;
|
|
1976
|
+
if ("multiple" in element && element.multiple) {
|
|
1977
|
+
if (!Array.isArray(value)) {
|
|
1978
|
+
console.warn(
|
|
1979
|
+
`updateSwitcherField: Expected array for multiple field "${fieldPath}", got ${typeof value}`
|
|
1980
|
+
);
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
const inputs = scopeRoot.querySelectorAll(
|
|
1984
|
+
`input[type="hidden"][name^="${fieldPath}["]`
|
|
1985
|
+
);
|
|
1986
|
+
inputs.forEach((input, index) => {
|
|
1987
|
+
var _a2;
|
|
1988
|
+
if (index < value.length) {
|
|
1989
|
+
const newVal = value[index] != null ? String(value[index]) : "";
|
|
1990
|
+
input.value = newVal;
|
|
1991
|
+
const group = (_a2 = input.parentElement) == null ? void 0 : _a2.querySelector(".fb-switcher-group");
|
|
1992
|
+
if (group) {
|
|
1993
|
+
group.querySelectorAll(".fb-switcher-btn").forEach((btn) => {
|
|
1994
|
+
if (btn.dataset.value === newVal) {
|
|
1995
|
+
applySelectedStyle(btn);
|
|
1996
|
+
} else {
|
|
1997
|
+
applyUnselectedStyle(btn);
|
|
1998
|
+
}
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
input.classList.remove("invalid");
|
|
2002
|
+
input.title = "";
|
|
2003
|
+
}
|
|
2004
|
+
});
|
|
2005
|
+
if (value.length !== inputs.length) {
|
|
1550
2006
|
console.warn(
|
|
1551
|
-
`
|
|
2007
|
+
`updateSwitcherField: Multiple field "${fieldPath}" has ${inputs.length} inputs but received ${value.length} values. Consider re-rendering for add/remove.`
|
|
1552
2008
|
);
|
|
1553
2009
|
}
|
|
1554
2010
|
} else {
|
|
1555
|
-
const
|
|
1556
|
-
`[name="${fieldPath}"]`
|
|
2011
|
+
const input = scopeRoot.querySelector(
|
|
2012
|
+
`input[type="hidden"][name="${fieldPath}"]`
|
|
1557
2013
|
);
|
|
1558
|
-
if (
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
2014
|
+
if (input) {
|
|
2015
|
+
const newVal = value != null ? String(value) : "";
|
|
2016
|
+
input.value = newVal;
|
|
2017
|
+
const group = (_a = input.parentElement) == null ? void 0 : _a.querySelector(".fb-switcher-group");
|
|
2018
|
+
if (group) {
|
|
2019
|
+
group.querySelectorAll(".fb-switcher-btn").forEach((btn) => {
|
|
2020
|
+
if (btn.dataset.value === newVal) {
|
|
2021
|
+
applySelectedStyle(btn);
|
|
2022
|
+
} else {
|
|
2023
|
+
applyUnselectedStyle(btn);
|
|
2024
|
+
}
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
input.classList.remove("invalid");
|
|
2028
|
+
input.title = "";
|
|
1566
2029
|
}
|
|
1567
2030
|
}
|
|
1568
2031
|
}
|
|
@@ -4309,6 +4772,1027 @@ function updateGroupField(element, fieldPath, value, context) {
|
|
|
4309
4772
|
return updateContainerField(containerElement, fieldPath, value, context);
|
|
4310
4773
|
}
|
|
4311
4774
|
|
|
4775
|
+
// src/components/table.ts
|
|
4776
|
+
function createEmptyCells(rows, cols) {
|
|
4777
|
+
return Array.from(
|
|
4778
|
+
{ length: rows },
|
|
4779
|
+
() => Array.from({ length: cols }, () => "")
|
|
4780
|
+
);
|
|
4781
|
+
}
|
|
4782
|
+
function getShadowingMerge(row, col, merges) {
|
|
4783
|
+
for (const m of merges) {
|
|
4784
|
+
if (m.row === row && m.col === col) {
|
|
4785
|
+
return null;
|
|
4786
|
+
}
|
|
4787
|
+
if (row >= m.row && row < m.row + m.rowspan && col >= m.col && col < m.col + m.colspan) {
|
|
4788
|
+
return m;
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4791
|
+
return null;
|
|
4792
|
+
}
|
|
4793
|
+
function getMergeAt(row, col, merges) {
|
|
4794
|
+
var _a;
|
|
4795
|
+
return (_a = merges.find((m) => m.row === row && m.col === col)) != null ? _a : null;
|
|
4796
|
+
}
|
|
4797
|
+
function selectionRange(sel) {
|
|
4798
|
+
var _a;
|
|
4799
|
+
if (!sel.anchor) return null;
|
|
4800
|
+
const focus = (_a = sel.focus) != null ? _a : sel.anchor;
|
|
4801
|
+
return {
|
|
4802
|
+
r1: Math.min(sel.anchor.row, focus.row),
|
|
4803
|
+
c1: Math.min(sel.anchor.col, focus.col),
|
|
4804
|
+
r2: Math.max(sel.anchor.row, focus.row),
|
|
4805
|
+
c2: Math.max(sel.anchor.col, focus.col)
|
|
4806
|
+
};
|
|
4807
|
+
}
|
|
4808
|
+
function makeOverlayCircleBtn(opts) {
|
|
4809
|
+
const btn = document.createElement("button");
|
|
4810
|
+
btn.type = "button";
|
|
4811
|
+
btn.textContent = opts.label;
|
|
4812
|
+
btn.title = opts.title;
|
|
4813
|
+
btn.style.cssText = `
|
|
4814
|
+
position: absolute;
|
|
4815
|
+
width: ${opts.size}px;
|
|
4816
|
+
height: ${opts.size}px;
|
|
4817
|
+
border-radius: 50%;
|
|
4818
|
+
border: none;
|
|
4819
|
+
background: ${opts.color};
|
|
4820
|
+
color: ${opts.textColor};
|
|
4821
|
+
font-size: ${Math.floor(opts.size * 0.6)}px;
|
|
4822
|
+
line-height: ${opts.size}px;
|
|
4823
|
+
text-align: center;
|
|
4824
|
+
cursor: pointer;
|
|
4825
|
+
z-index: 10;
|
|
4826
|
+
padding: 0;
|
|
4827
|
+
display: flex;
|
|
4828
|
+
align-items: center;
|
|
4829
|
+
justify-content: center;
|
|
4830
|
+
box-shadow: 0 1px 4px rgba(0,0,0,0.2);
|
|
4831
|
+
transition: transform 0.1s, opacity 0.1s;
|
|
4832
|
+
pointer-events: all;
|
|
4833
|
+
`;
|
|
4834
|
+
btn.addEventListener("mouseenter", () => {
|
|
4835
|
+
btn.style.transform = "scale(1.15)";
|
|
4836
|
+
});
|
|
4837
|
+
btn.addEventListener("mouseleave", () => {
|
|
4838
|
+
btn.style.transform = "scale(1)";
|
|
4839
|
+
});
|
|
4840
|
+
btn.addEventListener("click", (e) => {
|
|
4841
|
+
e.stopPropagation();
|
|
4842
|
+
opts.onClick(e);
|
|
4843
|
+
});
|
|
4844
|
+
return btn;
|
|
4845
|
+
}
|
|
4846
|
+
function renderReadonlyTable(data, wrapper) {
|
|
4847
|
+
const { cells, merges = [] } = data;
|
|
4848
|
+
if (cells.length === 0) return;
|
|
4849
|
+
const numCols = cells[0].length;
|
|
4850
|
+
const tableEl = document.createElement("table");
|
|
4851
|
+
tableEl.style.cssText = `
|
|
4852
|
+
width: 100%;
|
|
4853
|
+
border-collapse: collapse;
|
|
4854
|
+
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
4855
|
+
border-radius: var(--fb-border-radius);
|
|
4856
|
+
font-size: var(--fb-font-size);
|
|
4857
|
+
font-family: var(--fb-font-family);
|
|
4858
|
+
color: var(--fb-text-color);
|
|
4859
|
+
`;
|
|
4860
|
+
cells.forEach((rowData, rIdx) => {
|
|
4861
|
+
var _a, _b;
|
|
4862
|
+
const section = rIdx === 0 ? tableEl.createTHead() : (_a = tableEl.tBodies[0]) != null ? _a : tableEl.createTBody();
|
|
4863
|
+
const tr = section.insertRow();
|
|
4864
|
+
for (let cIdx = 0; cIdx < numCols; cIdx++) {
|
|
4865
|
+
if (getShadowingMerge(rIdx, cIdx, merges)) {
|
|
4866
|
+
continue;
|
|
4867
|
+
}
|
|
4868
|
+
const merge = getMergeAt(rIdx, cIdx, merges);
|
|
4869
|
+
const td = document.createElement(rIdx === 0 ? "th" : "td");
|
|
4870
|
+
if (merge) {
|
|
4871
|
+
if (merge.rowspan > 1) td.rowSpan = merge.rowspan;
|
|
4872
|
+
if (merge.colspan > 1) td.colSpan = merge.colspan;
|
|
4873
|
+
}
|
|
4874
|
+
td.textContent = (_b = rowData[cIdx]) != null ? _b : "";
|
|
4875
|
+
td.style.cssText = `
|
|
4876
|
+
padding: 6px 10px;
|
|
4877
|
+
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
4878
|
+
text-align: left;
|
|
4879
|
+
vertical-align: top;
|
|
4880
|
+
${rIdx === 0 ? "background-color: var(--fb-background-hover-color); font-weight: 600;" : ""}
|
|
4881
|
+
`;
|
|
4882
|
+
tr.appendChild(td);
|
|
4883
|
+
}
|
|
4884
|
+
});
|
|
4885
|
+
const scrollWrapper = document.createElement("div");
|
|
4886
|
+
scrollWrapper.style.cssText = "overflow-x: auto; max-width: 100%;";
|
|
4887
|
+
scrollWrapper.appendChild(tableEl);
|
|
4888
|
+
wrapper.appendChild(scrollWrapper);
|
|
4889
|
+
}
|
|
4890
|
+
function startCellEditing(span, r, c, getCells, persistValue, selectCell) {
|
|
4891
|
+
if (span.contentEditable === "true") return;
|
|
4892
|
+
span.contentEditable = "true";
|
|
4893
|
+
span.focus();
|
|
4894
|
+
const domRange = document.createRange();
|
|
4895
|
+
const winSel = window.getSelection();
|
|
4896
|
+
domRange.selectNodeContents(span);
|
|
4897
|
+
domRange.collapse(false);
|
|
4898
|
+
winSel == null ? void 0 : winSel.removeAllRanges();
|
|
4899
|
+
winSel == null ? void 0 : winSel.addRange(domRange);
|
|
4900
|
+
function commit() {
|
|
4901
|
+
var _a;
|
|
4902
|
+
span.contentEditable = "inherit";
|
|
4903
|
+
const cells = getCells();
|
|
4904
|
+
if (cells[r]) {
|
|
4905
|
+
cells[r][c] = (_a = span.textContent) != null ? _a : "";
|
|
4906
|
+
}
|
|
4907
|
+
persistValue();
|
|
4908
|
+
}
|
|
4909
|
+
function onKeyDown(e) {
|
|
4910
|
+
var _a, _b, _c, _d;
|
|
4911
|
+
const cells = getCells();
|
|
4912
|
+
const numCols = (_b = (_a = cells[0]) == null ? void 0 : _a.length) != null ? _b : 0;
|
|
4913
|
+
if (e.key === "Escape") {
|
|
4914
|
+
span.contentEditable = "inherit";
|
|
4915
|
+
span.textContent = (_d = (_c = cells[r]) == null ? void 0 : _c[c]) != null ? _d : "";
|
|
4916
|
+
span.removeEventListener("keydown", onKeyDown);
|
|
4917
|
+
span.removeEventListener("blur", onBlur);
|
|
4918
|
+
return;
|
|
4919
|
+
}
|
|
4920
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
4921
|
+
e.preventDefault();
|
|
4922
|
+
e.stopPropagation();
|
|
4923
|
+
commit();
|
|
4924
|
+
span.removeEventListener("keydown", onKeyDown);
|
|
4925
|
+
span.removeEventListener("blur", onBlur);
|
|
4926
|
+
const nextRow = r + 1 < cells.length ? r + 1 : r;
|
|
4927
|
+
selectCell(nextRow, c);
|
|
4928
|
+
return;
|
|
4929
|
+
}
|
|
4930
|
+
if (e.key === "Tab") {
|
|
4931
|
+
e.preventDefault();
|
|
4932
|
+
e.stopPropagation();
|
|
4933
|
+
commit();
|
|
4934
|
+
span.removeEventListener("keydown", onKeyDown);
|
|
4935
|
+
span.removeEventListener("blur", onBlur);
|
|
4936
|
+
let nr = r;
|
|
4937
|
+
let nc = e.shiftKey ? c - 1 : c + 1;
|
|
4938
|
+
if (nc < 0) {
|
|
4939
|
+
nc = numCols - 1;
|
|
4940
|
+
nr = Math.max(0, r - 1);
|
|
4941
|
+
}
|
|
4942
|
+
if (nc >= numCols) {
|
|
4943
|
+
nc = 0;
|
|
4944
|
+
nr = Math.min(cells.length - 1, r + 1);
|
|
4945
|
+
}
|
|
4946
|
+
selectCell(nr, nc);
|
|
4947
|
+
}
|
|
4948
|
+
}
|
|
4949
|
+
function onBlur() {
|
|
4950
|
+
if (span.contentEditable === "true") {
|
|
4951
|
+
commit();
|
|
4952
|
+
span.removeEventListener("keydown", onKeyDown);
|
|
4953
|
+
span.removeEventListener("blur", onBlur);
|
|
4954
|
+
}
|
|
4955
|
+
}
|
|
4956
|
+
span.addEventListener("keydown", onKeyDown);
|
|
4957
|
+
span.addEventListener("blur", onBlur);
|
|
4958
|
+
}
|
|
4959
|
+
function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
|
|
4960
|
+
var _a, _b;
|
|
4961
|
+
const state = ctx.state;
|
|
4962
|
+
const instance = ctx.instance;
|
|
4963
|
+
const cells = initialData.cells.length > 0 ? initialData.cells.map((r) => [...r]) : createEmptyCells((_a = element.rows) != null ? _a : 3, (_b = element.columns) != null ? _b : 3);
|
|
4964
|
+
let merges = initialData.merges ? [...initialData.merges] : [];
|
|
4965
|
+
const sel = { anchor: null, focus: null, dragging: false };
|
|
4966
|
+
const hiddenInput = document.createElement("input");
|
|
4967
|
+
hiddenInput.type = "hidden";
|
|
4968
|
+
hiddenInput.name = pathKey;
|
|
4969
|
+
hiddenInput.value = JSON.stringify({ cells, merges });
|
|
4970
|
+
wrapper.appendChild(hiddenInput);
|
|
4971
|
+
function persistValue() {
|
|
4972
|
+
hiddenInput.value = JSON.stringify({ cells, merges });
|
|
4973
|
+
if (instance) {
|
|
4974
|
+
instance.triggerOnChange(pathKey, { cells, merges });
|
|
4975
|
+
}
|
|
4976
|
+
}
|
|
4977
|
+
hiddenInput._applyExternalUpdate = (data) => {
|
|
4978
|
+
cells.length = 0;
|
|
4979
|
+
data.cells.forEach((row) => cells.push([...row]));
|
|
4980
|
+
merges.length = 0;
|
|
4981
|
+
if (data.merges) {
|
|
4982
|
+
data.merges.forEach((m) => merges.push({ ...m }));
|
|
4983
|
+
}
|
|
4984
|
+
sel.anchor = null;
|
|
4985
|
+
sel.focus = null;
|
|
4986
|
+
persistValue();
|
|
4987
|
+
rebuild();
|
|
4988
|
+
};
|
|
4989
|
+
const tableWrapper = document.createElement("div");
|
|
4990
|
+
tableWrapper.style.cssText = "position: relative; padding: 20px 20px 20px 24px; overflow-x: auto; max-width: 100%;";
|
|
4991
|
+
const tableEl = document.createElement("table");
|
|
4992
|
+
tableEl.style.cssText = `
|
|
4993
|
+
border-collapse: collapse;
|
|
4994
|
+
font-size: var(--fb-font-size);
|
|
4995
|
+
font-family: var(--fb-font-family);
|
|
4996
|
+
color: var(--fb-text-color);
|
|
4997
|
+
table-layout: fixed;
|
|
4998
|
+
`;
|
|
4999
|
+
tableWrapper.appendChild(tableEl);
|
|
5000
|
+
wrapper.appendChild(tableWrapper);
|
|
5001
|
+
const contextMenu = document.createElement("div");
|
|
5002
|
+
contextMenu.style.cssText = `
|
|
5003
|
+
position: fixed;
|
|
5004
|
+
display: none;
|
|
5005
|
+
background: white;
|
|
5006
|
+
border: 1px solid var(--fb-border-color);
|
|
5007
|
+
border-radius: var(--fb-border-radius);
|
|
5008
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
5009
|
+
padding: 4px;
|
|
5010
|
+
z-index: 1000;
|
|
5011
|
+
gap: 4px;
|
|
5012
|
+
flex-direction: column;
|
|
5013
|
+
`;
|
|
5014
|
+
wrapper.appendChild(contextMenu);
|
|
5015
|
+
function makeContextMenuBtn(label, onClick) {
|
|
5016
|
+
const btn = document.createElement("button");
|
|
5017
|
+
btn.type = "button";
|
|
5018
|
+
btn.textContent = label;
|
|
5019
|
+
btn.style.cssText = `
|
|
5020
|
+
padding: 4px 10px;
|
|
5021
|
+
font-size: var(--fb-font-size-small);
|
|
5022
|
+
color: var(--fb-text-color);
|
|
5023
|
+
border: 1px solid var(--fb-border-color);
|
|
5024
|
+
border-radius: var(--fb-border-radius);
|
|
5025
|
+
background: transparent;
|
|
5026
|
+
cursor: pointer;
|
|
5027
|
+
white-space: nowrap;
|
|
5028
|
+
text-align: left;
|
|
5029
|
+
`;
|
|
5030
|
+
btn.addEventListener("mouseenter", () => {
|
|
5031
|
+
btn.style.background = "var(--fb-background-hover-color)";
|
|
5032
|
+
});
|
|
5033
|
+
btn.addEventListener("mouseleave", () => {
|
|
5034
|
+
btn.style.background = "transparent";
|
|
5035
|
+
});
|
|
5036
|
+
btn.addEventListener("click", () => {
|
|
5037
|
+
hideContextMenu();
|
|
5038
|
+
onClick();
|
|
5039
|
+
});
|
|
5040
|
+
return btn;
|
|
5041
|
+
}
|
|
5042
|
+
function showContextMenu(x, y) {
|
|
5043
|
+
contextMenu.innerHTML = "";
|
|
5044
|
+
contextMenu.style.display = "flex";
|
|
5045
|
+
const range = selectionRange(sel);
|
|
5046
|
+
const isMultiCell = range && (range.r1 !== range.r2 || range.c1 !== range.c2);
|
|
5047
|
+
const isSingleMerged = sel.anchor && getMergeAt(sel.anchor.row, sel.anchor.col, merges);
|
|
5048
|
+
if (isMultiCell) {
|
|
5049
|
+
contextMenu.appendChild(
|
|
5050
|
+
makeContextMenuBtn(t("tableMergeCells", state), mergeCells)
|
|
5051
|
+
);
|
|
5052
|
+
}
|
|
5053
|
+
if (isSingleMerged) {
|
|
5054
|
+
contextMenu.appendChild(
|
|
5055
|
+
makeContextMenuBtn(t("tableSplitCell", state), splitCell)
|
|
5056
|
+
);
|
|
5057
|
+
}
|
|
5058
|
+
if (!contextMenu.firstChild) {
|
|
5059
|
+
hideContextMenu();
|
|
5060
|
+
return;
|
|
5061
|
+
}
|
|
5062
|
+
const menuWidth = 140;
|
|
5063
|
+
const menuHeight = contextMenu.children.length * 32 + 8;
|
|
5064
|
+
const vw = window.innerWidth;
|
|
5065
|
+
const vh = window.innerHeight;
|
|
5066
|
+
const left = x + menuWidth > vw ? x - menuWidth : x;
|
|
5067
|
+
const top = y + menuHeight > vh ? y - menuHeight : y;
|
|
5068
|
+
contextMenu.style.left = `${left}px`;
|
|
5069
|
+
contextMenu.style.top = `${top}px`;
|
|
5070
|
+
}
|
|
5071
|
+
function hideContextMenu() {
|
|
5072
|
+
contextMenu.style.display = "none";
|
|
5073
|
+
}
|
|
5074
|
+
const menuDismissCtrl = new AbortController();
|
|
5075
|
+
document.addEventListener("mousedown", (e) => {
|
|
5076
|
+
if (!wrapper.isConnected) {
|
|
5077
|
+
menuDismissCtrl.abort();
|
|
5078
|
+
return;
|
|
5079
|
+
}
|
|
5080
|
+
if (!contextMenu.contains(e.target)) {
|
|
5081
|
+
hideContextMenu();
|
|
5082
|
+
}
|
|
5083
|
+
}, { signal: menuDismissCtrl.signal });
|
|
5084
|
+
function applySelectionStyles() {
|
|
5085
|
+
const range = selectionRange(sel);
|
|
5086
|
+
const allTds = tableEl.querySelectorAll("td[data-row]");
|
|
5087
|
+
allTds.forEach((td) => {
|
|
5088
|
+
const r = parseInt(td.getAttribute("data-row") || "0", 10);
|
|
5089
|
+
const c = parseInt(td.getAttribute("data-col") || "0", 10);
|
|
5090
|
+
const isAnchor = sel.anchor !== null && sel.anchor.row === r && sel.anchor.col === c;
|
|
5091
|
+
const inRange = range !== null && r >= range.r1 && r <= range.r2 && c >= range.c1 && c <= range.c2;
|
|
5092
|
+
td.style.outline = isAnchor ? "2px solid var(--fb-primary-color, #0066cc)" : "";
|
|
5093
|
+
td.style.outlineOffset = isAnchor ? "-2px" : "";
|
|
5094
|
+
if (r === 0) {
|
|
5095
|
+
td.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
5096
|
+
} else {
|
|
5097
|
+
td.style.backgroundColor = inRange && !isAnchor ? "rgba(0,102,204,0.08)" : "";
|
|
5098
|
+
}
|
|
5099
|
+
});
|
|
5100
|
+
}
|
|
5101
|
+
function selectCell(row, col) {
|
|
5102
|
+
sel.anchor = { row, col };
|
|
5103
|
+
sel.focus = null;
|
|
5104
|
+
applySelectionStyles();
|
|
5105
|
+
tableEl.focus();
|
|
5106
|
+
}
|
|
5107
|
+
function editCell(row, col) {
|
|
5108
|
+
const td = tableEl.querySelector(
|
|
5109
|
+
`td[data-row="${row}"][data-col="${col}"]`
|
|
5110
|
+
);
|
|
5111
|
+
if (!td) return;
|
|
5112
|
+
const span = td.querySelector("span");
|
|
5113
|
+
if (!span) return;
|
|
5114
|
+
sel.anchor = { row, col };
|
|
5115
|
+
sel.focus = null;
|
|
5116
|
+
applySelectionStyles();
|
|
5117
|
+
startCellEditing(span, row, col, () => cells, persistValue, selectCell);
|
|
5118
|
+
}
|
|
5119
|
+
function addRow(afterIndex) {
|
|
5120
|
+
var _a2;
|
|
5121
|
+
const numCols = cells.length > 0 ? cells[0].length : (_a2 = element.columns) != null ? _a2 : 3;
|
|
5122
|
+
const newRow = Array(numCols).fill("");
|
|
5123
|
+
const insertAt = afterIndex !== void 0 ? afterIndex + 1 : cells.length;
|
|
5124
|
+
cells.splice(insertAt, 0, newRow);
|
|
5125
|
+
merges = merges.map((m) => {
|
|
5126
|
+
if (m.row >= insertAt) {
|
|
5127
|
+
return { ...m, row: m.row + 1 };
|
|
5128
|
+
}
|
|
5129
|
+
if (m.row < insertAt && m.row + m.rowspan > insertAt) {
|
|
5130
|
+
return { ...m, rowspan: m.rowspan + 1 };
|
|
5131
|
+
}
|
|
5132
|
+
return m;
|
|
5133
|
+
});
|
|
5134
|
+
persistValue();
|
|
5135
|
+
rebuild();
|
|
5136
|
+
}
|
|
5137
|
+
function removeRow(targetRow) {
|
|
5138
|
+
if (cells.length <= 1) return;
|
|
5139
|
+
const rowToRemove = targetRow !== void 0 ? targetRow : sel.anchor ? sel.anchor.row : cells.length - 1;
|
|
5140
|
+
merges = merges.map((m) => {
|
|
5141
|
+
const mEndRow = m.row + m.rowspan - 1;
|
|
5142
|
+
if (m.row === rowToRemove && m.rowspan === 1) return null;
|
|
5143
|
+
if (m.row === rowToRemove) {
|
|
5144
|
+
return { ...m, row: m.row + 1, rowspan: m.rowspan - 1 };
|
|
5145
|
+
}
|
|
5146
|
+
if (mEndRow === rowToRemove) {
|
|
5147
|
+
return { ...m, rowspan: m.rowspan - 1 };
|
|
5148
|
+
}
|
|
5149
|
+
if (m.row < rowToRemove && mEndRow > rowToRemove) {
|
|
5150
|
+
return { ...m, rowspan: m.rowspan - 1 };
|
|
5151
|
+
}
|
|
5152
|
+
if (m.row > rowToRemove) {
|
|
5153
|
+
return { ...m, row: m.row - 1 };
|
|
5154
|
+
}
|
|
5155
|
+
return m;
|
|
5156
|
+
}).filter((m) => m !== null);
|
|
5157
|
+
cells.splice(rowToRemove, 1);
|
|
5158
|
+
if (sel.anchor && sel.anchor.row >= cells.length) {
|
|
5159
|
+
sel.anchor = { row: cells.length - 1, col: sel.anchor.col };
|
|
5160
|
+
}
|
|
5161
|
+
persistValue();
|
|
5162
|
+
rebuild();
|
|
5163
|
+
}
|
|
5164
|
+
function addColumn(afterIndex) {
|
|
5165
|
+
var _a2, _b2;
|
|
5166
|
+
const insertAt = afterIndex !== void 0 ? afterIndex + 1 : (_b2 = (_a2 = cells[0]) == null ? void 0 : _a2.length) != null ? _b2 : 0;
|
|
5167
|
+
cells.forEach((row) => row.splice(insertAt, 0, ""));
|
|
5168
|
+
merges = merges.map((m) => {
|
|
5169
|
+
if (m.col >= insertAt) {
|
|
5170
|
+
return { ...m, col: m.col + 1 };
|
|
5171
|
+
}
|
|
5172
|
+
if (m.col < insertAt && m.col + m.colspan > insertAt) {
|
|
5173
|
+
return { ...m, colspan: m.colspan + 1 };
|
|
5174
|
+
}
|
|
5175
|
+
return m;
|
|
5176
|
+
});
|
|
5177
|
+
persistValue();
|
|
5178
|
+
rebuild();
|
|
5179
|
+
}
|
|
5180
|
+
function removeColumn(targetCol) {
|
|
5181
|
+
if (cells.length === 0 || cells[0].length <= 1) return;
|
|
5182
|
+
const colToRemove = targetCol !== void 0 ? targetCol : sel.anchor ? sel.anchor.col : cells[0].length - 1;
|
|
5183
|
+
merges = merges.map((m) => {
|
|
5184
|
+
const mEndCol = m.col + m.colspan - 1;
|
|
5185
|
+
if (m.col === colToRemove && m.colspan === 1) return null;
|
|
5186
|
+
if (m.col === colToRemove) {
|
|
5187
|
+
return { ...m, col: m.col + 1, colspan: m.colspan - 1 };
|
|
5188
|
+
}
|
|
5189
|
+
if (mEndCol === colToRemove) {
|
|
5190
|
+
return { ...m, colspan: m.colspan - 1 };
|
|
5191
|
+
}
|
|
5192
|
+
if (m.col < colToRemove && mEndCol > colToRemove) {
|
|
5193
|
+
return { ...m, colspan: m.colspan - 1 };
|
|
5194
|
+
}
|
|
5195
|
+
if (m.col > colToRemove) {
|
|
5196
|
+
return { ...m, col: m.col - 1 };
|
|
5197
|
+
}
|
|
5198
|
+
return m;
|
|
5199
|
+
}).filter((m) => m !== null);
|
|
5200
|
+
cells.forEach((row) => row.splice(colToRemove, 1));
|
|
5201
|
+
if (sel.anchor && sel.anchor.col >= cells[0].length) {
|
|
5202
|
+
sel.anchor = { row: sel.anchor.row, col: cells[0].length - 1 };
|
|
5203
|
+
}
|
|
5204
|
+
persistValue();
|
|
5205
|
+
rebuild();
|
|
5206
|
+
}
|
|
5207
|
+
function mergeCells() {
|
|
5208
|
+
const range = selectionRange(sel);
|
|
5209
|
+
if (!range) return;
|
|
5210
|
+
const { r1, c1, r2, c2 } = range;
|
|
5211
|
+
if (r1 === r2 && c1 === c2) return;
|
|
5212
|
+
merges = merges.filter((m) => {
|
|
5213
|
+
const mEndRow = m.row + m.rowspan - 1;
|
|
5214
|
+
const mEndCol = m.col + m.colspan - 1;
|
|
5215
|
+
const overlaps = m.row <= r2 && mEndRow >= r1 && m.col <= c2 && mEndCol >= c1;
|
|
5216
|
+
return !overlaps;
|
|
5217
|
+
});
|
|
5218
|
+
const anchorText = cells[r1][c1];
|
|
5219
|
+
for (let r = r1; r <= r2; r++) {
|
|
5220
|
+
for (let c = c1; c <= c2; c++) {
|
|
5221
|
+
if (r !== r1 || c !== c1) {
|
|
5222
|
+
cells[r][c] = "";
|
|
5223
|
+
}
|
|
5224
|
+
}
|
|
5225
|
+
}
|
|
5226
|
+
cells[r1][c1] = anchorText;
|
|
5227
|
+
merges.push({ row: r1, col: c1, rowspan: r2 - r1 + 1, colspan: c2 - c1 + 1 });
|
|
5228
|
+
sel.anchor = { row: r1, col: c1 };
|
|
5229
|
+
sel.focus = null;
|
|
5230
|
+
persistValue();
|
|
5231
|
+
rebuild();
|
|
5232
|
+
}
|
|
5233
|
+
function splitCell() {
|
|
5234
|
+
if (!sel.anchor) return;
|
|
5235
|
+
const { row, col } = sel.anchor;
|
|
5236
|
+
const mIdx = merges.findIndex((m) => m.row === row && m.col === col);
|
|
5237
|
+
if (mIdx === -1) return;
|
|
5238
|
+
merges.splice(mIdx, 1);
|
|
5239
|
+
sel.focus = null;
|
|
5240
|
+
persistValue();
|
|
5241
|
+
rebuild();
|
|
5242
|
+
}
|
|
5243
|
+
function rebuild() {
|
|
5244
|
+
var _a2, _b2;
|
|
5245
|
+
tableEl.innerHTML = "";
|
|
5246
|
+
const numRows = cells.length;
|
|
5247
|
+
const numCols = numRows > 0 ? cells[0].length : 0;
|
|
5248
|
+
const range = selectionRange(sel);
|
|
5249
|
+
for (let rIdx = 0; rIdx < numRows; rIdx++) {
|
|
5250
|
+
const section = rIdx === 0 ? (_a2 = tableEl.tHead) != null ? _a2 : tableEl.createTHead() : (_b2 = tableEl.tBodies[0]) != null ? _b2 : tableEl.createTBody();
|
|
5251
|
+
const tr = section.insertRow();
|
|
5252
|
+
for (let cIdx = 0; cIdx < numCols; cIdx++) {
|
|
5253
|
+
if (getShadowingMerge(rIdx, cIdx, merges)) {
|
|
5254
|
+
continue;
|
|
5255
|
+
}
|
|
5256
|
+
const merge = getMergeAt(rIdx, cIdx, merges);
|
|
5257
|
+
const td = document.createElement("td");
|
|
5258
|
+
td.setAttribute("data-row", String(rIdx));
|
|
5259
|
+
td.setAttribute("data-col", String(cIdx));
|
|
5260
|
+
if (merge) {
|
|
5261
|
+
if (merge.rowspan > 1) td.rowSpan = merge.rowspan;
|
|
5262
|
+
if (merge.colspan > 1) td.colSpan = merge.colspan;
|
|
5263
|
+
}
|
|
5264
|
+
const inRange = range !== null && rIdx >= range.r1 && rIdx <= range.r2 && cIdx >= range.c1 && cIdx <= range.c2;
|
|
5265
|
+
const isAnchor = sel.anchor !== null && sel.anchor.row === rIdx && sel.anchor.col === cIdx;
|
|
5266
|
+
td.style.cssText = [
|
|
5267
|
+
"border: var(--fb-border-width) solid var(--fb-border-color);",
|
|
5268
|
+
"padding: 4px 8px;",
|
|
5269
|
+
"min-width: 80px;",
|
|
5270
|
+
"vertical-align: top;",
|
|
5271
|
+
"cursor: text;",
|
|
5272
|
+
"position: relative;",
|
|
5273
|
+
rIdx === 0 ? "background-color: var(--fb-background-hover-color); font-weight: 600;" : "",
|
|
5274
|
+
inRange && !isAnchor ? "background-color: rgba(0,102,204,0.08);" : "",
|
|
5275
|
+
isAnchor ? "outline: 2px solid var(--fb-primary-color, #0066cc); outline-offset: -2px;" : ""
|
|
5276
|
+
].join(" ");
|
|
5277
|
+
const content = document.createElement("span");
|
|
5278
|
+
content.textContent = cells[rIdx][cIdx];
|
|
5279
|
+
content.style.cssText = "display: block; min-height: 1.4em; white-space: pre-wrap; word-break: break-word; outline: none;";
|
|
5280
|
+
td.appendChild(content);
|
|
5281
|
+
const capturedR = rIdx;
|
|
5282
|
+
const capturedC = cIdx;
|
|
5283
|
+
td.addEventListener("mousedown", (e) => {
|
|
5284
|
+
if (e.target.tagName === "BUTTON") return;
|
|
5285
|
+
if (e.target.contentEditable === "true") return;
|
|
5286
|
+
if (e.button === 2) {
|
|
5287
|
+
const range2 = selectionRange(sel);
|
|
5288
|
+
if (range2 && capturedR >= range2.r1 && capturedR <= range2.r2 && capturedC >= range2.c1 && capturedC <= range2.c2) {
|
|
5289
|
+
return;
|
|
5290
|
+
}
|
|
5291
|
+
}
|
|
5292
|
+
if (e.shiftKey && sel.anchor) {
|
|
5293
|
+
e.preventDefault();
|
|
5294
|
+
sel.focus = { row: capturedR, col: capturedC };
|
|
5295
|
+
sel.dragging = false;
|
|
5296
|
+
applySelectionStyles();
|
|
5297
|
+
} else {
|
|
5298
|
+
sel.anchor = { row: capturedR, col: capturedC };
|
|
5299
|
+
sel.focus = null;
|
|
5300
|
+
sel.dragging = true;
|
|
5301
|
+
applySelectionStyles();
|
|
5302
|
+
}
|
|
5303
|
+
});
|
|
5304
|
+
td.addEventListener("mouseup", (e) => {
|
|
5305
|
+
if (e.target.tagName === "BUTTON") return;
|
|
5306
|
+
if (sel.dragging) {
|
|
5307
|
+
sel.dragging = false;
|
|
5308
|
+
const currentRange = selectionRange(sel);
|
|
5309
|
+
const isSingleCell = !currentRange || currentRange.r1 === currentRange.r2 && currentRange.c1 === currentRange.c2;
|
|
5310
|
+
if (isSingleCell) {
|
|
5311
|
+
editCell(capturedR, capturedC);
|
|
5312
|
+
}
|
|
5313
|
+
}
|
|
5314
|
+
});
|
|
5315
|
+
td.addEventListener("mousemove", (e) => {
|
|
5316
|
+
if (sel.dragging && e.buttons === 1) {
|
|
5317
|
+
const currentAnchor = sel.anchor;
|
|
5318
|
+
if (currentAnchor && (currentAnchor.row !== capturedR || currentAnchor.col !== capturedC)) {
|
|
5319
|
+
sel.focus = { row: capturedR, col: capturedC };
|
|
5320
|
+
applySelectionStyles();
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
5323
|
+
});
|
|
5324
|
+
td.addEventListener("contextmenu", (e) => {
|
|
5325
|
+
const currentRange = selectionRange(sel);
|
|
5326
|
+
const isMulti = currentRange && (currentRange.r1 !== currentRange.r2 || currentRange.c1 !== currentRange.c2);
|
|
5327
|
+
const isMerged = sel.anchor && getMergeAt(sel.anchor.row, sel.anchor.col, merges);
|
|
5328
|
+
if (isMulti || isMerged) {
|
|
5329
|
+
e.preventDefault();
|
|
5330
|
+
showContextMenu(e.clientX, e.clientY);
|
|
5331
|
+
}
|
|
5332
|
+
});
|
|
5333
|
+
tr.appendChild(td);
|
|
5334
|
+
}
|
|
5335
|
+
}
|
|
5336
|
+
buildInsertOverlays();
|
|
5337
|
+
tableEl.setAttribute("tabindex", "0");
|
|
5338
|
+
tableEl.onkeydown = (e) => {
|
|
5339
|
+
var _a3, _b3;
|
|
5340
|
+
const editing = tableEl.querySelector("[contenteditable='true']");
|
|
5341
|
+
if (editing) return;
|
|
5342
|
+
const anchor = sel.anchor;
|
|
5343
|
+
if (!anchor) return;
|
|
5344
|
+
const numRows2 = cells.length;
|
|
5345
|
+
const numCols2 = numRows2 > 0 ? cells[0].length : 0;
|
|
5346
|
+
const navDeltas = {
|
|
5347
|
+
ArrowUp: [-1, 0],
|
|
5348
|
+
ArrowDown: [1, 0],
|
|
5349
|
+
ArrowLeft: [0, -1],
|
|
5350
|
+
ArrowRight: [0, 1]
|
|
5351
|
+
};
|
|
5352
|
+
if (navDeltas[e.key]) {
|
|
5353
|
+
e.preventDefault();
|
|
5354
|
+
const [dr, dc] = navDeltas[e.key];
|
|
5355
|
+
const newRow = Math.max(0, Math.min(numRows2 - 1, anchor.row + dr));
|
|
5356
|
+
const newCol = Math.max(0, Math.min(numCols2 - 1, anchor.col + dc));
|
|
5357
|
+
if (e.shiftKey) {
|
|
5358
|
+
sel.focus = { row: newRow, col: newCol };
|
|
5359
|
+
applySelectionStyles();
|
|
5360
|
+
} else {
|
|
5361
|
+
selectCell(newRow, newCol);
|
|
5362
|
+
}
|
|
5363
|
+
return;
|
|
5364
|
+
}
|
|
5365
|
+
if (e.key === "Enter") {
|
|
5366
|
+
e.preventDefault();
|
|
5367
|
+
editCell(anchor.row, anchor.col);
|
|
5368
|
+
return;
|
|
5369
|
+
}
|
|
5370
|
+
if (e.key === "Tab") {
|
|
5371
|
+
e.preventDefault();
|
|
5372
|
+
const numCols3 = (_b3 = (_a3 = cells[0]) == null ? void 0 : _a3.length) != null ? _b3 : 0;
|
|
5373
|
+
let nr = anchor.row;
|
|
5374
|
+
let nc = e.shiftKey ? anchor.col - 1 : anchor.col + 1;
|
|
5375
|
+
if (nc < 0) {
|
|
5376
|
+
nc = numCols3 - 1;
|
|
5377
|
+
nr = Math.max(0, nr - 1);
|
|
5378
|
+
}
|
|
5379
|
+
if (nc >= numCols3) {
|
|
5380
|
+
nc = 0;
|
|
5381
|
+
nr = Math.min(cells.length - 1, nr + 1);
|
|
5382
|
+
}
|
|
5383
|
+
selectCell(nr, nc);
|
|
5384
|
+
return;
|
|
5385
|
+
}
|
|
5386
|
+
if (e.key === "m" && e.ctrlKey && !e.shiftKey) {
|
|
5387
|
+
e.preventDefault();
|
|
5388
|
+
mergeCells();
|
|
5389
|
+
return;
|
|
5390
|
+
}
|
|
5391
|
+
if (e.key === "M" && e.ctrlKey && e.shiftKey) {
|
|
5392
|
+
e.preventDefault();
|
|
5393
|
+
splitCell();
|
|
5394
|
+
}
|
|
5395
|
+
};
|
|
5396
|
+
tableEl.oncopy = (e) => {
|
|
5397
|
+
var _a3, _b3, _c, _d;
|
|
5398
|
+
const range2 = selectionRange(sel);
|
|
5399
|
+
if (!range2) return;
|
|
5400
|
+
e.preventDefault();
|
|
5401
|
+
const { r1, c1, r2, c2 } = range2;
|
|
5402
|
+
const tsvRows = [];
|
|
5403
|
+
const htmlRows = [];
|
|
5404
|
+
for (let r = r1; r <= r2; r++) {
|
|
5405
|
+
const tsvCols = [];
|
|
5406
|
+
const htmlCols = [];
|
|
5407
|
+
for (let c = c1; c <= c2; c++) {
|
|
5408
|
+
const val = (_b3 = (_a3 = cells[r]) == null ? void 0 : _a3[c]) != null ? _b3 : "";
|
|
5409
|
+
tsvCols.push(val);
|
|
5410
|
+
const escaped = val.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
5411
|
+
htmlCols.push(`<td>${escaped}</td>`);
|
|
5412
|
+
}
|
|
5413
|
+
tsvRows.push(tsvCols.join(" "));
|
|
5414
|
+
htmlRows.push(`<tr>${htmlCols.join("")}</tr>`);
|
|
5415
|
+
}
|
|
5416
|
+
const tsvText = tsvRows.join("\n");
|
|
5417
|
+
const htmlText = `<table>${htmlRows.join("")}</table>`;
|
|
5418
|
+
(_c = e.clipboardData) == null ? void 0 : _c.setData("text/plain", tsvText);
|
|
5419
|
+
(_d = e.clipboardData) == null ? void 0 : _d.setData("text/html", htmlText);
|
|
5420
|
+
};
|
|
5421
|
+
tableEl.onpaste = (e) => {
|
|
5422
|
+
var _a3, _b3, _c, _d, _e, _f, _g, _h, _i;
|
|
5423
|
+
const anchor = sel.anchor;
|
|
5424
|
+
if (!anchor) return;
|
|
5425
|
+
const text = (_b3 = (_a3 = e.clipboardData) == null ? void 0 : _a3.getData("text/plain")) != null ? _b3 : "";
|
|
5426
|
+
const isMultiCell = text.includes(" ") || text.split(/\r?\n/).filter((l) => l).length > 1;
|
|
5427
|
+
const editing = tableEl.querySelector("[contenteditable='true']");
|
|
5428
|
+
if (editing && !isMultiCell) return;
|
|
5429
|
+
e.preventDefault();
|
|
5430
|
+
if (editing) {
|
|
5431
|
+
editing.contentEditable = "inherit";
|
|
5432
|
+
const r = parseInt(
|
|
5433
|
+
(_d = (_c = editing.closest("td")) == null ? void 0 : _c.getAttribute("data-row")) != null ? _d : "0",
|
|
5434
|
+
10
|
|
5435
|
+
);
|
|
5436
|
+
const c = parseInt(
|
|
5437
|
+
(_f = (_e = editing.closest("td")) == null ? void 0 : _e.getAttribute("data-col")) != null ? _f : "0",
|
|
5438
|
+
10
|
|
5439
|
+
);
|
|
5440
|
+
if (cells[r]) {
|
|
5441
|
+
cells[r][c] = (_g = editing.textContent) != null ? _g : "";
|
|
5442
|
+
}
|
|
5443
|
+
}
|
|
5444
|
+
if (!text.trim()) return;
|
|
5445
|
+
const pasteRows = text.split(/\r?\n/).map((line) => line.split(" "));
|
|
5446
|
+
if (pasteRows.length > 1 && pasteRows[pasteRows.length - 1].length === 1 && pasteRows[pasteRows.length - 1][0] === "") {
|
|
5447
|
+
pasteRows.pop();
|
|
5448
|
+
}
|
|
5449
|
+
const startR = anchor.row;
|
|
5450
|
+
const startC = anchor.col;
|
|
5451
|
+
const neededRows = startR + pasteRows.length;
|
|
5452
|
+
while (cells.length < neededRows) {
|
|
5453
|
+
cells.push(Array((_i = (_h = cells[0]) == null ? void 0 : _h.length) != null ? _i : 1).fill(""));
|
|
5454
|
+
}
|
|
5455
|
+
const maxPasteCols = Math.max(...pasteRows.map((r) => r.length));
|
|
5456
|
+
const neededCols = startC + maxPasteCols;
|
|
5457
|
+
if (cells[0] && neededCols > cells[0].length) {
|
|
5458
|
+
const extraCols = neededCols - cells[0].length;
|
|
5459
|
+
cells.forEach((row) => {
|
|
5460
|
+
for (let i = 0; i < extraCols; i++) row.push("");
|
|
5461
|
+
});
|
|
5462
|
+
}
|
|
5463
|
+
for (let pr = 0; pr < pasteRows.length; pr++) {
|
|
5464
|
+
for (let pc = 0; pc < pasteRows[pr].length; pc++) {
|
|
5465
|
+
const tr = startR + pr;
|
|
5466
|
+
const tc = startC + pc;
|
|
5467
|
+
if (cells[tr]) {
|
|
5468
|
+
cells[tr][tc] = pasteRows[pr][pc];
|
|
5469
|
+
}
|
|
5470
|
+
}
|
|
5471
|
+
}
|
|
5472
|
+
persistValue();
|
|
5473
|
+
rebuild();
|
|
5474
|
+
};
|
|
5475
|
+
}
|
|
5476
|
+
const insertColBtn = makeOverlayCircleBtn({
|
|
5477
|
+
label: "+",
|
|
5478
|
+
title: t("tableAddColumn", state),
|
|
5479
|
+
size: 20,
|
|
5480
|
+
color: "var(--fb-primary-color, #0066cc)",
|
|
5481
|
+
textColor: "white",
|
|
5482
|
+
onClick: () => {
|
|
5483
|
+
var _a2;
|
|
5484
|
+
const afterIdx = parseInt((_a2 = insertColBtn.dataset.afterCol) != null ? _a2 : "0", 10);
|
|
5485
|
+
addColumn(afterIdx);
|
|
5486
|
+
}
|
|
5487
|
+
});
|
|
5488
|
+
insertColBtn.style.position = "absolute";
|
|
5489
|
+
insertColBtn.style.display = "none";
|
|
5490
|
+
tableWrapper.appendChild(insertColBtn);
|
|
5491
|
+
const insertRowBtn = makeOverlayCircleBtn({
|
|
5492
|
+
label: "+",
|
|
5493
|
+
title: t("tableAddRow", state),
|
|
5494
|
+
size: 20,
|
|
5495
|
+
color: "var(--fb-primary-color, #0066cc)",
|
|
5496
|
+
textColor: "white",
|
|
5497
|
+
onClick: () => {
|
|
5498
|
+
var _a2;
|
|
5499
|
+
const afterIdx = parseInt((_a2 = insertRowBtn.dataset.afterRow) != null ? _a2 : "0", 10);
|
|
5500
|
+
addRow(afterIdx);
|
|
5501
|
+
}
|
|
5502
|
+
});
|
|
5503
|
+
insertRowBtn.style.position = "absolute";
|
|
5504
|
+
insertRowBtn.style.display = "none";
|
|
5505
|
+
tableWrapper.appendChild(insertRowBtn);
|
|
5506
|
+
let colRemoveBtns = [];
|
|
5507
|
+
let rowRemoveBtns = [];
|
|
5508
|
+
const addLastColBtn = makeOverlayCircleBtn({
|
|
5509
|
+
label: "+",
|
|
5510
|
+
title: t("tableAddColumn", state),
|
|
5511
|
+
size: 20,
|
|
5512
|
+
color: "var(--fb-primary-color, #0066cc)",
|
|
5513
|
+
textColor: "white",
|
|
5514
|
+
onClick: () => addColumn()
|
|
5515
|
+
});
|
|
5516
|
+
addLastColBtn.style.position = "absolute";
|
|
5517
|
+
addLastColBtn.style.display = "none";
|
|
5518
|
+
tableWrapper.appendChild(addLastColBtn);
|
|
5519
|
+
const addLastRowBtn = makeOverlayCircleBtn({
|
|
5520
|
+
label: "+",
|
|
5521
|
+
title: t("tableAddRow", state),
|
|
5522
|
+
size: 20,
|
|
5523
|
+
color: "var(--fb-primary-color, #0066cc)",
|
|
5524
|
+
textColor: "white",
|
|
5525
|
+
onClick: () => addRow()
|
|
5526
|
+
});
|
|
5527
|
+
addLastRowBtn.style.position = "absolute";
|
|
5528
|
+
addLastRowBtn.style.display = "none";
|
|
5529
|
+
tableWrapper.appendChild(addLastRowBtn);
|
|
5530
|
+
function buildInsertOverlays() {
|
|
5531
|
+
var _a2, _b2;
|
|
5532
|
+
colRemoveBtns.forEach((b) => b.remove());
|
|
5533
|
+
colRemoveBtns = [];
|
|
5534
|
+
rowRemoveBtns.forEach((b) => b.remove());
|
|
5535
|
+
rowRemoveBtns = [];
|
|
5536
|
+
const numCols = cells.length > 0 ? cells[0].length : 0;
|
|
5537
|
+
const numRows = cells.length;
|
|
5538
|
+
if (numCols > 1) {
|
|
5539
|
+
const headerCells = Array.from(
|
|
5540
|
+
tableEl.querySelectorAll("thead td[data-col]")
|
|
5541
|
+
);
|
|
5542
|
+
for (const hc of headerCells) {
|
|
5543
|
+
const colIdx = parseInt((_a2 = hc.getAttribute("data-col")) != null ? _a2 : "0", 10);
|
|
5544
|
+
const btn = makeOverlayCircleBtn({
|
|
5545
|
+
label: "\xD7",
|
|
5546
|
+
title: t("tableRemoveColumn", state),
|
|
5547
|
+
size: 16,
|
|
5548
|
+
color: "var(--fb-error-color, #dc3545)",
|
|
5549
|
+
textColor: "white",
|
|
5550
|
+
onClick: () => removeColumn(colIdx)
|
|
5551
|
+
});
|
|
5552
|
+
btn.setAttribute("data-action", "remove-col");
|
|
5553
|
+
btn.setAttribute("data-col", String(colIdx));
|
|
5554
|
+
btn.style.position = "absolute";
|
|
5555
|
+
btn.style.display = "none";
|
|
5556
|
+
tableWrapper.appendChild(btn);
|
|
5557
|
+
colRemoveBtns.push(btn);
|
|
5558
|
+
}
|
|
5559
|
+
}
|
|
5560
|
+
if (numRows > 1) {
|
|
5561
|
+
const allRowElements = [
|
|
5562
|
+
...tableEl.tHead ? Array.from(tableEl.tHead.rows) : [],
|
|
5563
|
+
...tableEl.tBodies[0] ? Array.from(tableEl.tBodies[0].rows) : []
|
|
5564
|
+
].filter((r) => r.querySelector("td[data-row]"));
|
|
5565
|
+
for (const rowEl of allRowElements) {
|
|
5566
|
+
const firstTd = rowEl.querySelector("td[data-row]");
|
|
5567
|
+
if (!firstTd) continue;
|
|
5568
|
+
const rowIdx = parseInt((_b2 = firstTd.getAttribute("data-row")) != null ? _b2 : "0", 10);
|
|
5569
|
+
const btn = makeOverlayCircleBtn({
|
|
5570
|
+
label: "\xD7",
|
|
5571
|
+
title: t("tableRemoveRow", state),
|
|
5572
|
+
size: 16,
|
|
5573
|
+
color: "var(--fb-error-color, #dc3545)",
|
|
5574
|
+
textColor: "white",
|
|
5575
|
+
onClick: () => removeRow(rowIdx)
|
|
5576
|
+
});
|
|
5577
|
+
btn.setAttribute("data-action", "remove-row");
|
|
5578
|
+
btn.setAttribute("data-row", String(rowIdx));
|
|
5579
|
+
btn.style.position = "absolute";
|
|
5580
|
+
btn.style.display = "none";
|
|
5581
|
+
tableWrapper.appendChild(btn);
|
|
5582
|
+
rowRemoveBtns.push(btn);
|
|
5583
|
+
}
|
|
5584
|
+
}
|
|
5585
|
+
function updateTopZoneOverlays(mx, wr, tblR, scrollL, active) {
|
|
5586
|
+
var _a3;
|
|
5587
|
+
const headerCells = active ? Array.from(tableEl.querySelectorAll("thead td[data-col]")) : [];
|
|
5588
|
+
let closestColIdx = -1;
|
|
5589
|
+
let closestColDist = Infinity;
|
|
5590
|
+
let closestBorderX = -1;
|
|
5591
|
+
let closestAfterCol = -1;
|
|
5592
|
+
let closestBorderDist = Infinity;
|
|
5593
|
+
for (let i = 0; i < headerCells.length; i++) {
|
|
5594
|
+
const cellRect = headerCells[i].getBoundingClientRect();
|
|
5595
|
+
const centerX = (cellRect.left + cellRect.right) / 2;
|
|
5596
|
+
const dist = Math.abs(mx - centerX);
|
|
5597
|
+
if (dist < closestColDist) {
|
|
5598
|
+
closestColDist = dist;
|
|
5599
|
+
closestColIdx = i;
|
|
5600
|
+
}
|
|
5601
|
+
const borderDist = Math.abs(mx - cellRect.right);
|
|
5602
|
+
if (borderDist < closestBorderDist && borderDist < 20) {
|
|
5603
|
+
closestBorderDist = borderDist;
|
|
5604
|
+
closestBorderX = cellRect.right - wr.left + scrollL;
|
|
5605
|
+
closestAfterCol = parseInt((_a3 = headerCells[i].getAttribute("data-col")) != null ? _a3 : "0", 10);
|
|
5606
|
+
}
|
|
5607
|
+
}
|
|
5608
|
+
colRemoveBtns.forEach((btn, idx) => {
|
|
5609
|
+
if (!active || idx !== closestColIdx) {
|
|
5610
|
+
btn.style.display = "none";
|
|
5611
|
+
return;
|
|
5612
|
+
}
|
|
5613
|
+
const cellRect = headerCells[idx].getBoundingClientRect();
|
|
5614
|
+
const centerX = (cellRect.left + cellRect.right) / 2 - wr.left + scrollL;
|
|
5615
|
+
btn.style.left = `${centerX - 8}px`;
|
|
5616
|
+
btn.style.top = "2px";
|
|
5617
|
+
btn.style.display = "flex";
|
|
5618
|
+
});
|
|
5619
|
+
if (active && closestAfterCol >= 0) {
|
|
5620
|
+
insertColBtn.style.display = "flex";
|
|
5621
|
+
insertColBtn.style.left = `${closestBorderX - 10}px`;
|
|
5622
|
+
insertColBtn.style.top = `${tblR.top - wr.top - 10}px`;
|
|
5623
|
+
insertColBtn.dataset.afterCol = String(closestAfterCol);
|
|
5624
|
+
} else {
|
|
5625
|
+
insertColBtn.style.display = "none";
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
function updateLeftZoneOverlays(my, wr, tblR, scrollL, active) {
|
|
5629
|
+
var _a3;
|
|
5630
|
+
const allRowEls = [];
|
|
5631
|
+
if (active) {
|
|
5632
|
+
if (tableEl.tHead) {
|
|
5633
|
+
for (const row of Array.from(tableEl.tHead.rows)) {
|
|
5634
|
+
if (row.querySelector("td[data-row]")) allRowEls.push(row);
|
|
5635
|
+
}
|
|
5636
|
+
}
|
|
5637
|
+
if (tableEl.tBodies[0]) {
|
|
5638
|
+
for (const row of Array.from(tableEl.tBodies[0].rows)) {
|
|
5639
|
+
if (row.querySelector("td[data-row]")) allRowEls.push(row);
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5642
|
+
}
|
|
5643
|
+
let closestRowIdx = -1;
|
|
5644
|
+
let closestRowDist = Infinity;
|
|
5645
|
+
let closestBorderY = -1;
|
|
5646
|
+
let closestAfterRow = -1;
|
|
5647
|
+
let closestRowBorderDist = Infinity;
|
|
5648
|
+
for (let i = 0; i < allRowEls.length; i++) {
|
|
5649
|
+
const trRect = allRowEls[i].getBoundingClientRect();
|
|
5650
|
+
const centerY = (trRect.top + trRect.bottom) / 2;
|
|
5651
|
+
const dist = Math.abs(my - centerY);
|
|
5652
|
+
if (dist < closestRowDist) {
|
|
5653
|
+
closestRowDist = dist;
|
|
5654
|
+
closestRowIdx = i;
|
|
5655
|
+
}
|
|
5656
|
+
const borderDist = Math.abs(my - trRect.bottom);
|
|
5657
|
+
if (borderDist < closestRowBorderDist && borderDist < 14) {
|
|
5658
|
+
closestRowBorderDist = borderDist;
|
|
5659
|
+
closestBorderY = trRect.bottom - wr.top;
|
|
5660
|
+
const firstTd = allRowEls[i].querySelector("td[data-row]");
|
|
5661
|
+
closestAfterRow = parseInt((_a3 = firstTd == null ? void 0 : firstTd.getAttribute("data-row")) != null ? _a3 : "0", 10);
|
|
5662
|
+
}
|
|
5663
|
+
}
|
|
5664
|
+
rowRemoveBtns.forEach((btn, idx) => {
|
|
5665
|
+
if (!active || idx !== closestRowIdx) {
|
|
5666
|
+
btn.style.display = "none";
|
|
5667
|
+
return;
|
|
5668
|
+
}
|
|
5669
|
+
const trRect = allRowEls[idx].getBoundingClientRect();
|
|
5670
|
+
const centerY = (trRect.top + trRect.bottom) / 2 - wr.top;
|
|
5671
|
+
btn.style.left = "4px";
|
|
5672
|
+
btn.style.top = `${centerY - 8}px`;
|
|
5673
|
+
btn.style.display = "flex";
|
|
5674
|
+
});
|
|
5675
|
+
if (active && closestAfterRow >= 0) {
|
|
5676
|
+
insertRowBtn.style.display = "flex";
|
|
5677
|
+
insertRowBtn.style.top = `${closestBorderY - 10}px`;
|
|
5678
|
+
insertRowBtn.style.left = `${tblR.left - wr.left + scrollL - 10}px`;
|
|
5679
|
+
insertRowBtn.dataset.afterRow = String(closestAfterRow);
|
|
5680
|
+
} else {
|
|
5681
|
+
insertRowBtn.style.display = "none";
|
|
5682
|
+
}
|
|
5683
|
+
}
|
|
5684
|
+
let rafPending = false;
|
|
5685
|
+
tableWrapper.onmousemove = (e) => {
|
|
5686
|
+
const target = e.target;
|
|
5687
|
+
if (target.tagName === "BUTTON" && target.parentElement === tableWrapper) return;
|
|
5688
|
+
if (rafPending) return;
|
|
5689
|
+
rafPending = true;
|
|
5690
|
+
const mx = e.clientX;
|
|
5691
|
+
const my = e.clientY;
|
|
5692
|
+
requestAnimationFrame(() => {
|
|
5693
|
+
rafPending = false;
|
|
5694
|
+
const wr = tableWrapper.getBoundingClientRect();
|
|
5695
|
+
const tblR = tableEl.getBoundingClientRect();
|
|
5696
|
+
const scrollL = tableWrapper.scrollLeft;
|
|
5697
|
+
const inTopZone = my >= wr.top && my < tblR.top + 4;
|
|
5698
|
+
const inLeftZone = mx >= wr.left && mx < tblR.left + 4;
|
|
5699
|
+
const visibleRight = Math.min(tblR.right, wr.right);
|
|
5700
|
+
const inRightZone = mx > visibleRight - 20 && mx <= wr.right;
|
|
5701
|
+
const inBottomZone = my > tblR.bottom - 4 && my <= wr.bottom + 20;
|
|
5702
|
+
updateTopZoneOverlays(mx, wr, tblR, scrollL, inTopZone);
|
|
5703
|
+
updateLeftZoneOverlays(my, wr, tblR, scrollL, inLeftZone);
|
|
5704
|
+
addLastColBtn.style.display = inRightZone ? "flex" : "none";
|
|
5705
|
+
if (inRightZone) {
|
|
5706
|
+
addLastColBtn.style.left = `${wr.right - wr.left + scrollL - 20}px`;
|
|
5707
|
+
addLastColBtn.style.top = `${(tblR.top + tblR.bottom) / 2 - wr.top - 10}px`;
|
|
5708
|
+
}
|
|
5709
|
+
addLastRowBtn.style.display = inBottomZone ? "flex" : "none";
|
|
5710
|
+
if (inBottomZone) {
|
|
5711
|
+
const visibleCenterX = (wr.left + wr.right) / 2 - wr.left + scrollL;
|
|
5712
|
+
addLastRowBtn.style.left = `${visibleCenterX - 10}px`;
|
|
5713
|
+
addLastRowBtn.style.top = `${tblR.bottom - wr.top - 10}px`;
|
|
5714
|
+
}
|
|
5715
|
+
});
|
|
5716
|
+
};
|
|
5717
|
+
tableWrapper.onmouseleave = () => {
|
|
5718
|
+
colRemoveBtns.forEach((btn) => {
|
|
5719
|
+
btn.style.display = "none";
|
|
5720
|
+
});
|
|
5721
|
+
rowRemoveBtns.forEach((btn) => {
|
|
5722
|
+
btn.style.display = "none";
|
|
5723
|
+
});
|
|
5724
|
+
insertColBtn.style.display = "none";
|
|
5725
|
+
insertRowBtn.style.display = "none";
|
|
5726
|
+
addLastColBtn.style.display = "none";
|
|
5727
|
+
addLastRowBtn.style.display = "none";
|
|
5728
|
+
};
|
|
5729
|
+
}
|
|
5730
|
+
rebuild();
|
|
5731
|
+
}
|
|
5732
|
+
function defaultTableData(element) {
|
|
5733
|
+
var _a, _b;
|
|
5734
|
+
return {
|
|
5735
|
+
cells: createEmptyCells((_a = element.rows) != null ? _a : 3, (_b = element.columns) != null ? _b : 3),
|
|
5736
|
+
merges: []
|
|
5737
|
+
};
|
|
5738
|
+
}
|
|
5739
|
+
function isTableData(v) {
|
|
5740
|
+
return v !== null && typeof v === "object" && "cells" in v && Array.isArray(v.cells);
|
|
5741
|
+
}
|
|
5742
|
+
function renderTableElement(element, ctx, wrapper, pathKey) {
|
|
5743
|
+
const state = ctx.state;
|
|
5744
|
+
const rawPrefill = ctx.prefill[element.key];
|
|
5745
|
+
const initialData = isTableData(rawPrefill) ? rawPrefill : isTableData(element.default) ? element.default : defaultTableData(element);
|
|
5746
|
+
if (state.config.readonly) {
|
|
5747
|
+
renderReadonlyTable(initialData, wrapper);
|
|
5748
|
+
} else {
|
|
5749
|
+
renderEditTable(element, initialData, pathKey, ctx, wrapper);
|
|
5750
|
+
}
|
|
5751
|
+
}
|
|
5752
|
+
function validateTableElement(element, key, context) {
|
|
5753
|
+
const { scopeRoot, skipValidation } = context;
|
|
5754
|
+
const errors = [];
|
|
5755
|
+
const hiddenInput = scopeRoot.querySelector(
|
|
5756
|
+
`[name="${key}"]`
|
|
5757
|
+
);
|
|
5758
|
+
if (!hiddenInput) {
|
|
5759
|
+
return { value: null, errors };
|
|
5760
|
+
}
|
|
5761
|
+
let value = null;
|
|
5762
|
+
try {
|
|
5763
|
+
value = JSON.parse(hiddenInput.value);
|
|
5764
|
+
} catch {
|
|
5765
|
+
errors.push(`${key}: invalid table data`);
|
|
5766
|
+
return { value: null, errors };
|
|
5767
|
+
}
|
|
5768
|
+
if (!skipValidation && element.required) {
|
|
5769
|
+
const hasContent = value.cells.some(
|
|
5770
|
+
(row) => row.some((cell) => cell.trim() !== "")
|
|
5771
|
+
);
|
|
5772
|
+
if (!hasContent) {
|
|
5773
|
+
errors.push(`${key}: ${t("required", context.state)}`);
|
|
5774
|
+
}
|
|
5775
|
+
}
|
|
5776
|
+
return { value, errors };
|
|
5777
|
+
}
|
|
5778
|
+
function updateTableField(_element, fieldPath, value, context) {
|
|
5779
|
+
const { scopeRoot } = context;
|
|
5780
|
+
const hiddenInput = scopeRoot.querySelector(
|
|
5781
|
+
`[name="${fieldPath}"]`
|
|
5782
|
+
);
|
|
5783
|
+
if (!hiddenInput) {
|
|
5784
|
+
console.warn(
|
|
5785
|
+
`updateTableField: no hidden input found for "${fieldPath}". Re-render to reflect new data.`
|
|
5786
|
+
);
|
|
5787
|
+
return;
|
|
5788
|
+
}
|
|
5789
|
+
if (isTableData(value) && hiddenInput._applyExternalUpdate) {
|
|
5790
|
+
hiddenInput._applyExternalUpdate(value);
|
|
5791
|
+
} else {
|
|
5792
|
+
hiddenInput.value = JSON.stringify(value);
|
|
5793
|
+
}
|
|
5794
|
+
}
|
|
5795
|
+
|
|
4312
5796
|
// src/components/index.ts
|
|
4313
5797
|
function showTooltip(tooltipId, button) {
|
|
4314
5798
|
const tooltip = document.getElementById(tooltipId);
|
|
@@ -4617,6 +6101,13 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
|
|
|
4617
6101
|
renderSelectElement(element, ctx, wrapper, pathKey);
|
|
4618
6102
|
}
|
|
4619
6103
|
break;
|
|
6104
|
+
case "switcher":
|
|
6105
|
+
if (isMultiple) {
|
|
6106
|
+
renderMultipleSwitcherElement(element, ctx, wrapper, pathKey);
|
|
6107
|
+
} else {
|
|
6108
|
+
renderSwitcherElement(element, ctx, wrapper, pathKey);
|
|
6109
|
+
}
|
|
6110
|
+
break;
|
|
4620
6111
|
case "file":
|
|
4621
6112
|
if (isMultiple) {
|
|
4622
6113
|
renderMultipleFileElement(element, ctx, wrapper, pathKey);
|
|
@@ -4651,6 +6142,9 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
|
|
|
4651
6142
|
renderSingleContainerElement(element, ctx, wrapper, pathKey);
|
|
4652
6143
|
}
|
|
4653
6144
|
break;
|
|
6145
|
+
case "table":
|
|
6146
|
+
renderTableElement(element, ctx, wrapper, pathKey);
|
|
6147
|
+
break;
|
|
4654
6148
|
default: {
|
|
4655
6149
|
const unsupported = document.createElement("div");
|
|
4656
6150
|
unsupported.className = "text-red-500 text-sm";
|
|
@@ -4740,7 +6234,14 @@ var defaultConfig = {
|
|
|
4740
6234
|
invalidHexColour: "Invalid hex color",
|
|
4741
6235
|
minFiles: "Minimum {min} files required",
|
|
4742
6236
|
maxFiles: "Maximum {max} files allowed",
|
|
4743
|
-
unsupportedFieldType: "Unsupported field type: {type}"
|
|
6237
|
+
unsupportedFieldType: "Unsupported field type: {type}",
|
|
6238
|
+
invalidOption: "Invalid option",
|
|
6239
|
+
tableAddRow: "Add row",
|
|
6240
|
+
tableAddColumn: "Add column",
|
|
6241
|
+
tableRemoveRow: "Remove row",
|
|
6242
|
+
tableRemoveColumn: "Remove column",
|
|
6243
|
+
tableMergeCells: "Merge cells (Ctrl+M)",
|
|
6244
|
+
tableSplitCell: "Split cell (Ctrl+Shift+M)"
|
|
4744
6245
|
},
|
|
4745
6246
|
ru: {
|
|
4746
6247
|
// UI texts
|
|
@@ -4785,7 +6286,14 @@ var defaultConfig = {
|
|
|
4785
6286
|
invalidHexColour: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u0446\u0432\u0435\u0442\u0430",
|
|
4786
6287
|
minFiles: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
4787
6288
|
maxFiles: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
4788
|
-
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}"
|
|
6289
|
+
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}",
|
|
6290
|
+
invalidOption: "\u041D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435",
|
|
6291
|
+
tableAddRow: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0443",
|
|
6292
|
+
tableAddColumn: "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0441\u0442\u043E\u043B\u0431\u0435\u0446",
|
|
6293
|
+
tableRemoveRow: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0443",
|
|
6294
|
+
tableRemoveColumn: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0442\u043E\u043B\u0431\u0435\u0446",
|
|
6295
|
+
tableMergeCells: "\u041E\u0431\u044A\u0435\u0434\u0438\u043D\u0438\u0442\u044C \u044F\u0447\u0435\u0439\u043A\u0438 (Ctrl+M)",
|
|
6296
|
+
tableSplitCell: "\u0420\u0430\u0437\u0434\u0435\u043B\u0438\u0442\u044C \u044F\u0447\u0435\u0439\u043A\u0443 (Ctrl+Shift+M)"
|
|
4789
6297
|
}
|
|
4790
6298
|
},
|
|
4791
6299
|
theme: {}
|
|
@@ -5030,6 +6538,10 @@ var componentRegistry = {
|
|
|
5030
6538
|
validate: validateSelectElement,
|
|
5031
6539
|
update: updateSelectField
|
|
5032
6540
|
},
|
|
6541
|
+
switcher: {
|
|
6542
|
+
validate: validateSwitcherElement,
|
|
6543
|
+
update: updateSwitcherField
|
|
6544
|
+
},
|
|
5033
6545
|
file: {
|
|
5034
6546
|
validate: validateFileElement,
|
|
5035
6547
|
update: updateFileField
|
|
@@ -5055,6 +6567,10 @@ var componentRegistry = {
|
|
|
5055
6567
|
// Deprecated type - delegates to container
|
|
5056
6568
|
validate: validateGroupElement,
|
|
5057
6569
|
update: updateGroupField
|
|
6570
|
+
},
|
|
6571
|
+
table: {
|
|
6572
|
+
validate: validateTableElement,
|
|
6573
|
+
update: updateTableField
|
|
5058
6574
|
}
|
|
5059
6575
|
};
|
|
5060
6576
|
function getComponentOperations(elementType) {
|