@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/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.row === row && m.col === col) {
4760
+ if (m.top === row && m.left === col) {
4725
4761
  return null;
4726
4762
  }
4727
- if (row >= m.row && row < m.row + m.rowspan && col >= m.col && col < m.col + m.colspan) {
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.row === row && m.col === col) ?? null;
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
- if (merge.rowspan > 1) td.rowSpan = merge.rowspan;
4809
- if (merge.colspan > 1) td.colSpan = merge.colspan;
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.row >= insertAt) {
5060
- return { ...m, row: m.row + 1 };
5239
+ if (m.top >= insertAt) {
5240
+ return { ...m, top: m.top + 1, bottom: m.bottom + 1 };
5061
5241
  }
5062
- if (m.row < insertAt && m.row + m.rowspan > insertAt) {
5063
- return { ...m, rowspan: m.rowspan + 1 };
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
- const mEndRow = m.row + m.rowspan - 1;
5075
- if (m.row === rowToRemove && m.rowspan === 1) return null;
5076
- if (m.row === rowToRemove) {
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 (mEndRow === rowToRemove) {
5080
- return { ...m, rowspan: m.rowspan - 1 };
5258
+ if (m.bottom === rowToRemove) {
5259
+ return { ...m, bottom: m.bottom - 1 };
5081
5260
  }
5082
- if (m.row < rowToRemove && mEndRow > rowToRemove) {
5083
- return { ...m, rowspan: m.rowspan - 1 };
5261
+ if (m.top < rowToRemove && m.bottom > rowToRemove) {
5262
+ return { ...m, bottom: m.bottom - 1 };
5084
5263
  }
5085
- if (m.row > rowToRemove) {
5086
- return { ...m, row: m.row - 1 };
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.col >= insertAt) {
5102
- return { ...m, col: m.col + 1 };
5280
+ if (m.left >= insertAt) {
5281
+ return { ...m, left: m.left + 1, right: m.right + 1 };
5103
5282
  }
5104
- if (m.col < insertAt && m.col + m.colspan > insertAt) {
5105
- return { ...m, colspan: m.colspan + 1 };
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
- const mEndCol = m.col + m.colspan - 1;
5117
- if (m.col === colToRemove && m.colspan === 1) return null;
5118
- if (m.col === colToRemove) {
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 (mEndCol === colToRemove) {
5122
- return { ...m, colspan: m.colspan - 1 };
5299
+ if (m.right === colToRemove) {
5300
+ return { ...m, right: m.right - 1 };
5123
5301
  }
5124
- if (m.col < colToRemove && mEndCol > colToRemove) {
5125
- return { ...m, colspan: m.colspan - 1 };
5302
+ if (m.left < colToRemove && m.right > colToRemove) {
5303
+ return { ...m, right: m.right - 1 };
5126
5304
  }
5127
- if (m.col > colToRemove) {
5128
- return { ...m, col: m.col - 1 };
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 mEndRow = m.row + m.rowspan - 1;
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
- merges.push({ row: r1, col: c1, rowspan: r2 - r1 + 1, colspan: c2 - c1 + 1 });
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.row === row && m.col === col);
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
- if (merge.rowspan > 1) td.rowSpan = merge.rowspan;
5193
- if (merge.colspan > 1) td.colSpan = merge.colspan;
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 initialData = isTableData(rawPrefill) ? rawPrefill : isTableData(element.default) ? element.default : defaultTableData(element);
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 hasContent = value.cells.some(
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(_element, fieldPath, value, context) {
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
- if (isTableData(value) && hiddenInput._applyExternalUpdate) {
5712
- hiddenInput._applyExternalUpdate(value);
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",