@dmitryvim/form-builder 0.2.20 → 0.2.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/formbuilder.min.js +138 -119
- package/dist/browser/formbuilder.v0.2.22.min.js +602 -0
- package/dist/cjs/index.cjs +318 -60
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +301 -50
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +138 -119
- package/dist/types/components/table.d.ts +1 -1
- package/dist/types/types/config.d.ts +8 -0
- package/dist/types/types/schema.d.ts +17 -8
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.2.20.min.js +0 -583
package/dist/esm/index.js
CHANGED
|
@@ -4713,25 +4713,61 @@ function updateGroupField(element, fieldPath, value, context) {
|
|
|
4713
4713
|
}
|
|
4714
4714
|
|
|
4715
4715
|
// src/components/table.ts
|
|
4716
|
+
function isLegacyMerge(m) {
|
|
4717
|
+
return m !== null && typeof m === "object" && "row" in m && "col" in m && "rowspan" in m && "colspan" in m;
|
|
4718
|
+
}
|
|
4719
|
+
function isValidMergeShape(m) {
|
|
4720
|
+
if (m === null || typeof m !== "object") return false;
|
|
4721
|
+
const o = m;
|
|
4722
|
+
return typeof o.top === "number" && typeof o.left === "number" && typeof o.bottom === "number" && typeof o.right === "number";
|
|
4723
|
+
}
|
|
4724
|
+
function migrateMerge(m) {
|
|
4725
|
+
if (isLegacyMerge(m)) {
|
|
4726
|
+
return {
|
|
4727
|
+
top: m.row,
|
|
4728
|
+
left: m.col,
|
|
4729
|
+
bottom: m.row + m.rowspan - 1,
|
|
4730
|
+
right: m.col + m.colspan - 1
|
|
4731
|
+
};
|
|
4732
|
+
}
|
|
4733
|
+
if (isValidMergeShape(m)) return m;
|
|
4734
|
+
return null;
|
|
4735
|
+
}
|
|
4736
|
+
function migrateMerges(merges) {
|
|
4737
|
+
return merges.map(migrateMerge).filter((m) => m !== null);
|
|
4738
|
+
}
|
|
4716
4739
|
function createEmptyCells(rows, cols) {
|
|
4717
4740
|
return Array.from(
|
|
4718
4741
|
{ length: rows },
|
|
4719
4742
|
() => Array.from({ length: cols }, () => "")
|
|
4720
4743
|
);
|
|
4721
4744
|
}
|
|
4745
|
+
function validateMerges(merges, rows, cols) {
|
|
4746
|
+
for (let i = 0; i < merges.length; i++) {
|
|
4747
|
+
const a = merges[i];
|
|
4748
|
+
if (a.top < 0 || a.left < 0 || a.bottom >= rows || a.right >= cols || a.top > a.bottom || a.left > a.right)
|
|
4749
|
+
return `Merge ${i} out of bounds`;
|
|
4750
|
+
for (let j = i + 1; j < merges.length; j++) {
|
|
4751
|
+
const b = merges[j];
|
|
4752
|
+
if (a.top <= b.bottom && a.bottom >= b.top && a.left <= b.right && a.right >= b.left)
|
|
4753
|
+
return `Merges ${i} and ${j} overlap`;
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
return null;
|
|
4757
|
+
}
|
|
4722
4758
|
function getShadowingMerge(row, col, merges) {
|
|
4723
4759
|
for (const m of merges) {
|
|
4724
|
-
if (m.
|
|
4760
|
+
if (m.top === row && m.left === col) {
|
|
4725
4761
|
return null;
|
|
4726
4762
|
}
|
|
4727
|
-
if (row >= m.
|
|
4763
|
+
if (row >= m.top && row <= m.bottom && col >= m.left && col <= m.right) {
|
|
4728
4764
|
return m;
|
|
4729
4765
|
}
|
|
4730
4766
|
}
|
|
4731
4767
|
return null;
|
|
4732
4768
|
}
|
|
4733
4769
|
function getMergeAt(row, col, merges) {
|
|
4734
|
-
return merges.find((m) => m.
|
|
4770
|
+
return merges.find((m) => m.top === row && m.left === col) ?? null;
|
|
4735
4771
|
}
|
|
4736
4772
|
function selectionRange(sel) {
|
|
4737
4773
|
if (!sel.anchor) return null;
|
|
@@ -4805,8 +4841,10 @@ function renderReadonlyTable(data, wrapper) {
|
|
|
4805
4841
|
const merge = getMergeAt(rIdx, cIdx, merges);
|
|
4806
4842
|
const td = document.createElement(rIdx === 0 ? "th" : "td");
|
|
4807
4843
|
if (merge) {
|
|
4808
|
-
|
|
4809
|
-
|
|
4844
|
+
const rowspan = merge.bottom - merge.top + 1;
|
|
4845
|
+
const colspan = merge.right - merge.left + 1;
|
|
4846
|
+
if (rowspan > 1) td.rowSpan = rowspan;
|
|
4847
|
+
if (colspan > 1) td.colSpan = colspan;
|
|
4810
4848
|
}
|
|
4811
4849
|
td.textContent = rowData[cIdx] ?? "";
|
|
4812
4850
|
td.style.cssText = `
|
|
@@ -4891,21 +4929,55 @@ function startCellEditing(span, r, c, getCells, persistValue, selectCell) {
|
|
|
4891
4929
|
span.addEventListener("keydown", onKeyDown);
|
|
4892
4930
|
span.addEventListener("blur", onBlur);
|
|
4893
4931
|
}
|
|
4932
|
+
function ensureSpinKeyframes() {
|
|
4933
|
+
if (document.getElementById("fb-spin-keyframes")) return;
|
|
4934
|
+
const style = document.createElement("style");
|
|
4935
|
+
style.id = "fb-spin-keyframes";
|
|
4936
|
+
style.textContent = `@keyframes fb-spin { to { transform: rotate(360deg); } }`;
|
|
4937
|
+
document.head.appendChild(style);
|
|
4938
|
+
}
|
|
4939
|
+
function showLoadingOverlay(parent, text) {
|
|
4940
|
+
ensureSpinKeyframes();
|
|
4941
|
+
const overlay = document.createElement("div");
|
|
4942
|
+
overlay.className = "fb-table-loading-overlay";
|
|
4943
|
+
overlay.style.cssText = `
|
|
4944
|
+
position: absolute; top: 0; left: 0; right: 0; bottom: 0;
|
|
4945
|
+
background: rgba(255,255,255,0.8); display: flex;
|
|
4946
|
+
align-items: center; justify-content: center; flex-direction: column;
|
|
4947
|
+
gap: 8px; z-index: 100;
|
|
4948
|
+
`;
|
|
4949
|
+
const spinner = document.createElement("div");
|
|
4950
|
+
spinner.style.cssText = `
|
|
4951
|
+
width: 24px; height: 24px; border: 3px solid var(--fb-border-color, #ccc);
|
|
4952
|
+
border-top-color: var(--fb-primary-color, #0066cc); border-radius: 50%;
|
|
4953
|
+
animation: fb-spin 0.8s linear infinite;
|
|
4954
|
+
`;
|
|
4955
|
+
const label = document.createElement("span");
|
|
4956
|
+
label.textContent = text;
|
|
4957
|
+
label.style.cssText = `font-size: var(--fb-font-size-small, 12px); color: var(--fb-text-color, #333);`;
|
|
4958
|
+
overlay.appendChild(spinner);
|
|
4959
|
+
overlay.appendChild(label);
|
|
4960
|
+
parent.appendChild(overlay);
|
|
4961
|
+
return overlay;
|
|
4962
|
+
}
|
|
4894
4963
|
function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
|
|
4895
4964
|
const state = ctx.state;
|
|
4896
4965
|
const instance = ctx.instance;
|
|
4966
|
+
const mergeAllowed = element.mergeAllowed !== false;
|
|
4967
|
+
const cellsKey = element.fieldNames?.cells ?? "cells";
|
|
4968
|
+
const mergesKey = element.fieldNames?.merges ?? "merges";
|
|
4897
4969
|
const cells = initialData.cells.length > 0 ? initialData.cells.map((r) => [...r]) : createEmptyCells(element.rows ?? 3, element.columns ?? 3);
|
|
4898
4970
|
let merges = initialData.merges ? [...initialData.merges] : [];
|
|
4899
4971
|
const sel = { anchor: null, focus: null, dragging: false };
|
|
4900
4972
|
const hiddenInput = document.createElement("input");
|
|
4901
4973
|
hiddenInput.type = "hidden";
|
|
4902
4974
|
hiddenInput.name = pathKey;
|
|
4903
|
-
hiddenInput.value = JSON.stringify({ cells, merges });
|
|
4975
|
+
hiddenInput.value = JSON.stringify({ [cellsKey]: cells, [mergesKey]: merges });
|
|
4904
4976
|
wrapper.appendChild(hiddenInput);
|
|
4905
4977
|
function persistValue() {
|
|
4906
|
-
hiddenInput.value = JSON.stringify({ cells, merges });
|
|
4978
|
+
hiddenInput.value = JSON.stringify({ [cellsKey]: cells, [mergesKey]: merges });
|
|
4907
4979
|
if (instance) {
|
|
4908
|
-
instance.triggerOnChange(pathKey, { cells, merges });
|
|
4980
|
+
instance.triggerOnChange(pathKey, { [cellsKey]: cells, [mergesKey]: merges });
|
|
4909
4981
|
}
|
|
4910
4982
|
}
|
|
4911
4983
|
hiddenInput._applyExternalUpdate = (data) => {
|
|
@@ -4931,6 +5003,113 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
|
|
|
4931
5003
|
table-layout: fixed;
|
|
4932
5004
|
`;
|
|
4933
5005
|
tableWrapper.appendChild(tableEl);
|
|
5006
|
+
const acceptExts = element.importAccept?.map((ext) => `.${ext.toLowerCase()}`) ?? [];
|
|
5007
|
+
async function importFile(file) {
|
|
5008
|
+
if (!state.config.parseTableFile) return;
|
|
5009
|
+
if (acceptExts.length > 0) {
|
|
5010
|
+
const ext = file.name.toLowerCase().replace(/^.*(\.[^.]+)$/, "$1");
|
|
5011
|
+
if (!acceptExts.includes(ext)) return;
|
|
5012
|
+
}
|
|
5013
|
+
const overlay = showLoadingOverlay(
|
|
5014
|
+
tableWrapper,
|
|
5015
|
+
t("tableImporting", state)
|
|
5016
|
+
);
|
|
5017
|
+
try {
|
|
5018
|
+
const result = await state.config.parseTableFile(file);
|
|
5019
|
+
const importedCells = result.cells;
|
|
5020
|
+
const importedMerges = result.merges ?? [];
|
|
5021
|
+
if (importedMerges.length > 0) {
|
|
5022
|
+
const err = validateMerges(
|
|
5023
|
+
importedMerges,
|
|
5024
|
+
importedCells.length,
|
|
5025
|
+
importedCells[0]?.length ?? 0
|
|
5026
|
+
);
|
|
5027
|
+
if (err) throw new Error(err);
|
|
5028
|
+
}
|
|
5029
|
+
cells.length = 0;
|
|
5030
|
+
importedCells.forEach((row) => cells.push([...row]));
|
|
5031
|
+
merges.length = 0;
|
|
5032
|
+
importedMerges.forEach((m) => merges.push({ ...m }));
|
|
5033
|
+
sel.anchor = null;
|
|
5034
|
+
sel.focus = null;
|
|
5035
|
+
persistValue();
|
|
5036
|
+
rebuild();
|
|
5037
|
+
} catch (e) {
|
|
5038
|
+
const errMsg = e instanceof Error ? e.message : String(e);
|
|
5039
|
+
console.error(
|
|
5040
|
+
t("tableImportError", state).replace("{error}", errMsg)
|
|
5041
|
+
);
|
|
5042
|
+
} finally {
|
|
5043
|
+
overlay.remove();
|
|
5044
|
+
}
|
|
5045
|
+
}
|
|
5046
|
+
if (element.importAccept && state.config.parseTableFile) {
|
|
5047
|
+
const importBtn = document.createElement("button");
|
|
5048
|
+
importBtn.type = "button";
|
|
5049
|
+
importBtn.title = t("tableImportFile", state);
|
|
5050
|
+
importBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48"/></svg>`;
|
|
5051
|
+
importBtn.style.cssText = `
|
|
5052
|
+
position: absolute; top: 2px; left: 2px;
|
|
5053
|
+
width: 20px; height: 20px;
|
|
5054
|
+
padding: 0;
|
|
5055
|
+
display: flex; align-items: center; justify-content: center;
|
|
5056
|
+
color: var(--fb-text-color, #999);
|
|
5057
|
+
border: none;
|
|
5058
|
+
background: transparent;
|
|
5059
|
+
cursor: pointer;
|
|
5060
|
+
z-index: 2;
|
|
5061
|
+
`;
|
|
5062
|
+
importBtn.addEventListener("mouseenter", () => {
|
|
5063
|
+
importBtn.style.color = "var(--fb-primary-color, #0066cc)";
|
|
5064
|
+
});
|
|
5065
|
+
importBtn.addEventListener("mouseleave", () => {
|
|
5066
|
+
importBtn.style.color = "var(--fb-text-color, #999)";
|
|
5067
|
+
});
|
|
5068
|
+
const importInput = document.createElement("input");
|
|
5069
|
+
importInput.type = "file";
|
|
5070
|
+
importInput.accept = acceptExts.join(",");
|
|
5071
|
+
importInput.style.display = "none";
|
|
5072
|
+
tableWrapper.appendChild(importInput);
|
|
5073
|
+
importBtn.addEventListener("click", () => {
|
|
5074
|
+
importInput.click();
|
|
5075
|
+
});
|
|
5076
|
+
importInput.addEventListener("change", () => {
|
|
5077
|
+
const file = importInput.files?.[0];
|
|
5078
|
+
if (file) importFile(file);
|
|
5079
|
+
importInput.value = "";
|
|
5080
|
+
});
|
|
5081
|
+
tableWrapper.appendChild(importBtn);
|
|
5082
|
+
let dragCounter = 0;
|
|
5083
|
+
tableWrapper.addEventListener("dragenter", (e) => {
|
|
5084
|
+
e.preventDefault();
|
|
5085
|
+
dragCounter++;
|
|
5086
|
+
if (dragCounter === 1) {
|
|
5087
|
+
tableWrapper.style.outline = "2px dashed var(--fb-primary-color, #0066cc)";
|
|
5088
|
+
tableWrapper.style.outlineOffset = "-2px";
|
|
5089
|
+
}
|
|
5090
|
+
});
|
|
5091
|
+
tableWrapper.addEventListener("dragover", (e) => {
|
|
5092
|
+
e.preventDefault();
|
|
5093
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
|
|
5094
|
+
});
|
|
5095
|
+
tableWrapper.addEventListener("dragleave", (e) => {
|
|
5096
|
+
e.preventDefault();
|
|
5097
|
+
dragCounter--;
|
|
5098
|
+
if (dragCounter <= 0) {
|
|
5099
|
+
dragCounter = 0;
|
|
5100
|
+
tableWrapper.style.outline = "";
|
|
5101
|
+
tableWrapper.style.outlineOffset = "";
|
|
5102
|
+
}
|
|
5103
|
+
});
|
|
5104
|
+
tableWrapper.addEventListener("drop", (e) => {
|
|
5105
|
+
e.preventDefault();
|
|
5106
|
+
dragCounter = 0;
|
|
5107
|
+
tableWrapper.style.outline = "";
|
|
5108
|
+
tableWrapper.style.outlineOffset = "";
|
|
5109
|
+
const file = e.dataTransfer?.files?.[0];
|
|
5110
|
+
if (file) importFile(file);
|
|
5111
|
+
});
|
|
5112
|
+
}
|
|
4934
5113
|
wrapper.appendChild(tableWrapper);
|
|
4935
5114
|
const contextMenu = document.createElement("div");
|
|
4936
5115
|
contextMenu.style.cssText = `
|
|
@@ -4974,6 +5153,7 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
|
|
|
4974
5153
|
return btn;
|
|
4975
5154
|
}
|
|
4976
5155
|
function showContextMenu(x, y) {
|
|
5156
|
+
if (!mergeAllowed) return;
|
|
4977
5157
|
contextMenu.innerHTML = "";
|
|
4978
5158
|
contextMenu.style.display = "flex";
|
|
4979
5159
|
const range = selectionRange(sel);
|
|
@@ -5056,11 +5236,11 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
|
|
|
5056
5236
|
const insertAt = afterIndex !== void 0 ? afterIndex + 1 : cells.length;
|
|
5057
5237
|
cells.splice(insertAt, 0, newRow);
|
|
5058
5238
|
merges = merges.map((m) => {
|
|
5059
|
-
if (m.
|
|
5060
|
-
return { ...m,
|
|
5239
|
+
if (m.top >= insertAt) {
|
|
5240
|
+
return { ...m, top: m.top + 1, bottom: m.bottom + 1 };
|
|
5061
5241
|
}
|
|
5062
|
-
if (m.
|
|
5063
|
-
return { ...m,
|
|
5242
|
+
if (m.top < insertAt && m.bottom >= insertAt) {
|
|
5243
|
+
return { ...m, bottom: m.bottom + 1 };
|
|
5064
5244
|
}
|
|
5065
5245
|
return m;
|
|
5066
5246
|
});
|
|
@@ -5071,19 +5251,18 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
|
|
|
5071
5251
|
if (cells.length <= 1) return;
|
|
5072
5252
|
const rowToRemove = targetRow !== void 0 ? targetRow : sel.anchor ? sel.anchor.row : cells.length - 1;
|
|
5073
5253
|
merges = merges.map((m) => {
|
|
5074
|
-
|
|
5075
|
-
if (m.
|
|
5076
|
-
|
|
5077
|
-
return { ...m, row: m.row + 1, rowspan: m.rowspan - 1 };
|
|
5254
|
+
if (m.top === rowToRemove && m.bottom === rowToRemove) return null;
|
|
5255
|
+
if (m.top === rowToRemove) {
|
|
5256
|
+
return { ...m, bottom: m.bottom - 1 };
|
|
5078
5257
|
}
|
|
5079
|
-
if (
|
|
5080
|
-
return { ...m,
|
|
5258
|
+
if (m.bottom === rowToRemove) {
|
|
5259
|
+
return { ...m, bottom: m.bottom - 1 };
|
|
5081
5260
|
}
|
|
5082
|
-
if (m.
|
|
5083
|
-
return { ...m,
|
|
5261
|
+
if (m.top < rowToRemove && m.bottom > rowToRemove) {
|
|
5262
|
+
return { ...m, bottom: m.bottom - 1 };
|
|
5084
5263
|
}
|
|
5085
|
-
if (m.
|
|
5086
|
-
return { ...m,
|
|
5264
|
+
if (m.top > rowToRemove) {
|
|
5265
|
+
return { ...m, top: m.top - 1, bottom: m.bottom - 1 };
|
|
5087
5266
|
}
|
|
5088
5267
|
return m;
|
|
5089
5268
|
}).filter((m) => m !== null);
|
|
@@ -5098,11 +5277,11 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
|
|
|
5098
5277
|
const insertAt = afterIndex !== void 0 ? afterIndex + 1 : cells[0]?.length ?? 0;
|
|
5099
5278
|
cells.forEach((row) => row.splice(insertAt, 0, ""));
|
|
5100
5279
|
merges = merges.map((m) => {
|
|
5101
|
-
if (m.
|
|
5102
|
-
return { ...m,
|
|
5280
|
+
if (m.left >= insertAt) {
|
|
5281
|
+
return { ...m, left: m.left + 1, right: m.right + 1 };
|
|
5103
5282
|
}
|
|
5104
|
-
if (m.
|
|
5105
|
-
return { ...m,
|
|
5283
|
+
if (m.left < insertAt && m.right >= insertAt) {
|
|
5284
|
+
return { ...m, right: m.right + 1 };
|
|
5106
5285
|
}
|
|
5107
5286
|
return m;
|
|
5108
5287
|
});
|
|
@@ -5113,19 +5292,18 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
|
|
|
5113
5292
|
if (cells.length === 0 || cells[0].length <= 1) return;
|
|
5114
5293
|
const colToRemove = targetCol !== void 0 ? targetCol : sel.anchor ? sel.anchor.col : cells[0].length - 1;
|
|
5115
5294
|
merges = merges.map((m) => {
|
|
5116
|
-
|
|
5117
|
-
if (m.
|
|
5118
|
-
|
|
5119
|
-
return { ...m, col: m.col + 1, colspan: m.colspan - 1 };
|
|
5295
|
+
if (m.left === colToRemove && m.right === colToRemove) return null;
|
|
5296
|
+
if (m.left === colToRemove) {
|
|
5297
|
+
return { ...m, right: m.right - 1 };
|
|
5120
5298
|
}
|
|
5121
|
-
if (
|
|
5122
|
-
return { ...m,
|
|
5299
|
+
if (m.right === colToRemove) {
|
|
5300
|
+
return { ...m, right: m.right - 1 };
|
|
5123
5301
|
}
|
|
5124
|
-
if (m.
|
|
5125
|
-
return { ...m,
|
|
5302
|
+
if (m.left < colToRemove && m.right > colToRemove) {
|
|
5303
|
+
return { ...m, right: m.right - 1 };
|
|
5126
5304
|
}
|
|
5127
|
-
if (m.
|
|
5128
|
-
return { ...m,
|
|
5305
|
+
if (m.left > colToRemove) {
|
|
5306
|
+
return { ...m, left: m.left - 1, right: m.right - 1 };
|
|
5129
5307
|
}
|
|
5130
5308
|
return m;
|
|
5131
5309
|
}).filter((m) => m !== null);
|
|
@@ -5137,14 +5315,13 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
|
|
|
5137
5315
|
rebuild();
|
|
5138
5316
|
}
|
|
5139
5317
|
function mergeCells() {
|
|
5318
|
+
if (!mergeAllowed) return;
|
|
5140
5319
|
const range = selectionRange(sel);
|
|
5141
5320
|
if (!range) return;
|
|
5142
5321
|
const { r1, c1, r2, c2 } = range;
|
|
5143
5322
|
if (r1 === r2 && c1 === c2) return;
|
|
5144
5323
|
merges = merges.filter((m) => {
|
|
5145
|
-
const
|
|
5146
|
-
const mEndCol = m.col + m.colspan - 1;
|
|
5147
|
-
const overlaps = m.row <= r2 && mEndRow >= r1 && m.col <= c2 && mEndCol >= c1;
|
|
5324
|
+
const overlaps = m.top <= r2 && m.bottom >= r1 && m.left <= c2 && m.right >= c1;
|
|
5148
5325
|
return !overlaps;
|
|
5149
5326
|
});
|
|
5150
5327
|
const anchorText = cells[r1][c1];
|
|
@@ -5156,16 +5333,24 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
|
|
|
5156
5333
|
}
|
|
5157
5334
|
}
|
|
5158
5335
|
cells[r1][c1] = anchorText;
|
|
5159
|
-
|
|
5336
|
+
const newMerge = { top: r1, left: c1, bottom: r2, right: c2 };
|
|
5337
|
+
const testMerges = [...merges, newMerge];
|
|
5338
|
+
const err = validateMerges(testMerges, cells.length, cells[0]?.length ?? 0);
|
|
5339
|
+
if (err) {
|
|
5340
|
+
console.warn("Merge validation failed:", err);
|
|
5341
|
+
return;
|
|
5342
|
+
}
|
|
5343
|
+
merges.push(newMerge);
|
|
5160
5344
|
sel.anchor = { row: r1, col: c1 };
|
|
5161
5345
|
sel.focus = null;
|
|
5162
5346
|
persistValue();
|
|
5163
5347
|
rebuild();
|
|
5164
5348
|
}
|
|
5165
5349
|
function splitCell() {
|
|
5350
|
+
if (!mergeAllowed) return;
|
|
5166
5351
|
if (!sel.anchor) return;
|
|
5167
5352
|
const { row, col } = sel.anchor;
|
|
5168
|
-
const mIdx = merges.findIndex((m) => m.
|
|
5353
|
+
const mIdx = merges.findIndex((m) => m.top === row && m.left === col);
|
|
5169
5354
|
if (mIdx === -1) return;
|
|
5170
5355
|
merges.splice(mIdx, 1);
|
|
5171
5356
|
sel.focus = null;
|
|
@@ -5189,8 +5374,10 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
|
|
|
5189
5374
|
td.setAttribute("data-row", String(rIdx));
|
|
5190
5375
|
td.setAttribute("data-col", String(cIdx));
|
|
5191
5376
|
if (merge) {
|
|
5192
|
-
|
|
5193
|
-
|
|
5377
|
+
const rowspan = merge.bottom - merge.top + 1;
|
|
5378
|
+
const colspan = merge.right - merge.left + 1;
|
|
5379
|
+
if (rowspan > 1) td.rowSpan = rowspan;
|
|
5380
|
+
if (colspan > 1) td.colSpan = colspan;
|
|
5194
5381
|
}
|
|
5195
5382
|
const inRange = range !== null && rIdx >= range.r1 && rIdx <= range.r2 && cIdx >= range.c1 && cIdx <= range.c2;
|
|
5196
5383
|
const isAnchor = sel.anchor !== null && sel.anchor.row === rIdx && sel.anchor.col === cIdx;
|
|
@@ -5253,6 +5440,7 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
|
|
|
5253
5440
|
}
|
|
5254
5441
|
});
|
|
5255
5442
|
td.addEventListener("contextmenu", (e) => {
|
|
5443
|
+
if (!mergeAllowed) return;
|
|
5256
5444
|
const currentRange = selectionRange(sel);
|
|
5257
5445
|
const isMulti = currentRange && (currentRange.r1 !== currentRange.r2 || currentRange.c1 !== currentRange.c2);
|
|
5258
5446
|
const isMerged = sel.anchor && getMergeAt(sel.anchor.row, sel.anchor.col, merges);
|
|
@@ -5313,12 +5501,12 @@ function renderEditTable(element, initialData, pathKey, ctx, wrapper) {
|
|
|
5313
5501
|
selectCell(nr, nc);
|
|
5314
5502
|
return;
|
|
5315
5503
|
}
|
|
5316
|
-
if (e.key === "m" && e.ctrlKey && !e.shiftKey) {
|
|
5504
|
+
if (mergeAllowed && e.key === "m" && e.ctrlKey && !e.shiftKey) {
|
|
5317
5505
|
e.preventDefault();
|
|
5318
5506
|
mergeCells();
|
|
5319
5507
|
return;
|
|
5320
5508
|
}
|
|
5321
|
-
if (e.key === "M" && e.ctrlKey && e.shiftKey) {
|
|
5509
|
+
if (mergeAllowed && e.key === "M" && e.ctrlKey && e.shiftKey) {
|
|
5322
5510
|
e.preventDefault();
|
|
5323
5511
|
splitCell();
|
|
5324
5512
|
}
|
|
@@ -5661,10 +5849,49 @@ function defaultTableData(element) {
|
|
|
5661
5849
|
function isTableData(v) {
|
|
5662
5850
|
return v !== null && typeof v === "object" && "cells" in v && Array.isArray(v.cells);
|
|
5663
5851
|
}
|
|
5852
|
+
function isTableDataWithFieldNames(v, cellsKey) {
|
|
5853
|
+
return v !== null && typeof v === "object" && cellsKey in v && Array.isArray(v[cellsKey]);
|
|
5854
|
+
}
|
|
5664
5855
|
function renderTableElement(element, ctx, wrapper, pathKey) {
|
|
5665
5856
|
const state = ctx.state;
|
|
5666
5857
|
const rawPrefill = ctx.prefill[element.key];
|
|
5667
|
-
const
|
|
5858
|
+
const cellsKey = element.fieldNames?.cells ?? "cells";
|
|
5859
|
+
const mergesKey = element.fieldNames?.merges ?? "merges";
|
|
5860
|
+
let initialData;
|
|
5861
|
+
if (isTableData(rawPrefill)) {
|
|
5862
|
+
initialData = {
|
|
5863
|
+
cells: rawPrefill.cells,
|
|
5864
|
+
merges: rawPrefill.merges ? migrateMerges(rawPrefill.merges) : []
|
|
5865
|
+
};
|
|
5866
|
+
} else if (rawPrefill && isTableDataWithFieldNames(rawPrefill, cellsKey)) {
|
|
5867
|
+
const rawMerges = rawPrefill[mergesKey];
|
|
5868
|
+
initialData = {
|
|
5869
|
+
cells: rawPrefill[cellsKey],
|
|
5870
|
+
merges: rawMerges ? migrateMerges(rawMerges) : []
|
|
5871
|
+
};
|
|
5872
|
+
} else if (isTableData(element.default)) {
|
|
5873
|
+
initialData = {
|
|
5874
|
+
cells: element.default.cells,
|
|
5875
|
+
merges: element.default.merges ? migrateMerges(element.default.merges) : []
|
|
5876
|
+
};
|
|
5877
|
+
} else if (element.default && isTableDataWithFieldNames(element.default, cellsKey)) {
|
|
5878
|
+
const rawMerges = element.default[mergesKey];
|
|
5879
|
+
initialData = {
|
|
5880
|
+
cells: element.default[cellsKey],
|
|
5881
|
+
merges: rawMerges ? migrateMerges(rawMerges) : []
|
|
5882
|
+
};
|
|
5883
|
+
} else {
|
|
5884
|
+
initialData = defaultTableData(element);
|
|
5885
|
+
}
|
|
5886
|
+
if (initialData.merges && initialData.merges.length > 0) {
|
|
5887
|
+
const rows = initialData.cells.length;
|
|
5888
|
+
const cols = rows > 0 ? initialData.cells[0].length : 0;
|
|
5889
|
+
const err = validateMerges(initialData.merges, rows, cols);
|
|
5890
|
+
if (err) {
|
|
5891
|
+
console.warn(`Table "${element.key}": invalid prefill merges stripped (${err})`);
|
|
5892
|
+
initialData = { ...initialData, merges: [] };
|
|
5893
|
+
}
|
|
5894
|
+
}
|
|
5668
5895
|
if (state.config.readonly) {
|
|
5669
5896
|
renderReadonlyTable(initialData, wrapper);
|
|
5670
5897
|
} else {
|
|
@@ -5674,6 +5901,7 @@ function renderTableElement(element, ctx, wrapper, pathKey) {
|
|
|
5674
5901
|
function validateTableElement(element, key, context) {
|
|
5675
5902
|
const { scopeRoot, skipValidation } = context;
|
|
5676
5903
|
const errors = [];
|
|
5904
|
+
const cellsKey = element.fieldNames?.cells ?? "cells";
|
|
5677
5905
|
const hiddenInput = scopeRoot.querySelector(
|
|
5678
5906
|
`[name="${key}"]`
|
|
5679
5907
|
);
|
|
@@ -5688,7 +5916,8 @@ function validateTableElement(element, key, context) {
|
|
|
5688
5916
|
return { value: null, errors };
|
|
5689
5917
|
}
|
|
5690
5918
|
if (!skipValidation && element.required) {
|
|
5691
|
-
const
|
|
5919
|
+
const cells = value[cellsKey];
|
|
5920
|
+
const hasContent = cells?.some(
|
|
5692
5921
|
(row) => row.some((cell) => cell.trim() !== "")
|
|
5693
5922
|
);
|
|
5694
5923
|
if (!hasContent) {
|
|
@@ -5697,8 +5926,10 @@ function validateTableElement(element, key, context) {
|
|
|
5697
5926
|
}
|
|
5698
5927
|
return { value, errors };
|
|
5699
5928
|
}
|
|
5700
|
-
function updateTableField(
|
|
5929
|
+
function updateTableField(element, fieldPath, value, context) {
|
|
5701
5930
|
const { scopeRoot } = context;
|
|
5931
|
+
const cellsKey = element.fieldNames?.cells ?? "cells";
|
|
5932
|
+
const mergesKey = element.fieldNames?.merges ?? "merges";
|
|
5702
5933
|
const hiddenInput = scopeRoot.querySelector(
|
|
5703
5934
|
`[name="${fieldPath}"]`
|
|
5704
5935
|
);
|
|
@@ -5708,8 +5939,21 @@ function updateTableField(_element, fieldPath, value, context) {
|
|
|
5708
5939
|
);
|
|
5709
5940
|
return;
|
|
5710
5941
|
}
|
|
5711
|
-
|
|
5712
|
-
|
|
5942
|
+
let tableData = null;
|
|
5943
|
+
if (isTableData(value)) {
|
|
5944
|
+
tableData = {
|
|
5945
|
+
cells: value.cells,
|
|
5946
|
+
merges: value.merges ? migrateMerges(value.merges) : []
|
|
5947
|
+
};
|
|
5948
|
+
} else if (value && isTableDataWithFieldNames(value, cellsKey)) {
|
|
5949
|
+
const rawMerges = value[mergesKey];
|
|
5950
|
+
tableData = {
|
|
5951
|
+
cells: value[cellsKey],
|
|
5952
|
+
merges: rawMerges ? migrateMerges(rawMerges) : []
|
|
5953
|
+
};
|
|
5954
|
+
}
|
|
5955
|
+
if (tableData && hiddenInput._applyExternalUpdate) {
|
|
5956
|
+
hiddenInput._applyExternalUpdate(tableData);
|
|
5713
5957
|
} else {
|
|
5714
5958
|
hiddenInput.value = JSON.stringify(value);
|
|
5715
5959
|
}
|
|
@@ -7378,6 +7622,7 @@ var defaultConfig = {
|
|
|
7378
7622
|
enableFilePreview: true,
|
|
7379
7623
|
maxPreviewSize: "200px",
|
|
7380
7624
|
readonly: false,
|
|
7625
|
+
parseTableFile: null,
|
|
7381
7626
|
locale: "en",
|
|
7382
7627
|
translations: {
|
|
7383
7628
|
en: {
|
|
@@ -7431,6 +7676,9 @@ var defaultConfig = {
|
|
|
7431
7676
|
tableRemoveColumn: "Remove column",
|
|
7432
7677
|
tableMergeCells: "Merge cells (Ctrl+M)",
|
|
7433
7678
|
tableSplitCell: "Split cell (Ctrl+Shift+M)",
|
|
7679
|
+
tableImportFile: "Import",
|
|
7680
|
+
tableImporting: "Importing...",
|
|
7681
|
+
tableImportError: "Import failed: {error}",
|
|
7434
7682
|
richinputPlaceholder: "Type text...",
|
|
7435
7683
|
richinputAttachFile: "Attach file",
|
|
7436
7684
|
richinputMention: "Mention",
|
|
@@ -7487,6 +7735,9 @@ var defaultConfig = {
|
|
|
7487
7735
|
tableRemoveColumn: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0442\u043E\u043B\u0431\u0435\u0446",
|
|
7488
7736
|
tableMergeCells: "\u041E\u0431\u044A\u0435\u0434\u0438\u043D\u0438\u0442\u044C \u044F\u0447\u0435\u0439\u043A\u0438 (Ctrl+M)",
|
|
7489
7737
|
tableSplitCell: "\u0420\u0430\u0437\u0434\u0435\u043B\u0438\u0442\u044C \u044F\u0447\u0435\u0439\u043A\u0443 (Ctrl+Shift+M)",
|
|
7738
|
+
tableImportFile: "\u0418\u043C\u043F\u043E\u0440\u0442",
|
|
7739
|
+
tableImporting: "\u0418\u043C\u043F\u043E\u0440\u0442...",
|
|
7740
|
+
tableImportError: "\u041E\u0448\u0438\u0431\u043A\u0430 \u0438\u043C\u043F\u043E\u0440\u0442\u0430: {error}",
|
|
7490
7741
|
richinputPlaceholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442...",
|
|
7491
7742
|
richinputAttachFile: "\u041F\u0440\u0438\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u0444\u0430\u0439\u043B",
|
|
7492
7743
|
richinputMention: "\u0423\u043F\u043E\u043C\u044F\u043D\u0443\u0442\u044C",
|