@underverse-ui/underverse 1.0.150 → 1.0.152

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/index.cjs CHANGED
@@ -1029,6 +1029,10 @@ var en_default = {
1029
1029
  borderWidth: "Border Width",
1030
1030
  borderColor: "Border Color",
1031
1031
  clearBorder: "Clear Border",
1032
+ formula: "Formula",
1033
+ apply: "Apply",
1034
+ clear: "Clear",
1035
+ recalculate: "Recalculate",
1032
1036
  done: "Done"
1033
1037
  },
1034
1038
  callout: {
@@ -1405,6 +1409,10 @@ var vi_default = {
1405
1409
  borderWidth: "\u0110\u1ED9 d\xE0y vi\u1EC1n",
1406
1410
  borderColor: "M\xE0u vi\u1EC1n",
1407
1411
  clearBorder: "X\xF3a vi\u1EC1n",
1412
+ formula: "C\xF4ng th\u1EE9c",
1413
+ apply: "\xC1p d\u1EE5ng",
1414
+ clear: "X\xF3a",
1415
+ recalculate: "T\xEDnh l\u1EA1i",
1408
1416
  done: "Xong"
1409
1417
  },
1410
1418
  callout: {
@@ -26140,6 +26148,10 @@ function getTableCellAttrs(cell, styles, defaultBackgroundColor) {
26140
26148
  if (borderAttrs.borderColor) attrs.borderColor = borderAttrs.borderColor;
26141
26149
  if (borderAttrs.borderStyle) attrs.borderStyle = borderAttrs.borderStyle;
26142
26150
  if (borderAttrs.borderWidth) attrs.borderWidth = borderAttrs.borderWidth;
26151
+ if (cell.getAttribute("data-cell-id")) attrs.cellId = cell.getAttribute("data-cell-id") ?? void 0;
26152
+ if (cell.getAttribute("data-number-format")) attrs.numberFormat = cell.getAttribute("data-number-format") ?? void 0;
26153
+ if (cell.getAttribute("data-formula")) attrs.formula = cell.getAttribute("data-formula") ?? void 0;
26154
+ if (cell.getAttribute("data-computed-value")) attrs.computedValue = cell.getAttribute("data-computed-value") ?? void 0;
26143
26155
  if (colspan > 1) attrs.colspan = colspan;
26144
26156
  if (rowspan > 1) attrs.rowspan = rowspan;
26145
26157
  if (colwidth) attrs.colwidth = colwidth;
@@ -27767,6 +27779,16 @@ var CodeBlockView = ({
27767
27779
  };
27768
27780
 
27769
27781
  // src/components/UEditor/extensions.ts
27782
+ function getFormulaStateAttributes(attributes) {
27783
+ const formula = attributes["data-formula"];
27784
+ if (!formula) {
27785
+ return {};
27786
+ }
27787
+ const computedValue = String(attributes["data-computed-value"] ?? "");
27788
+ return {
27789
+ "data-formula-state": computedValue.startsWith("#") ? "error" : "computed"
27790
+ };
27791
+ }
27770
27792
  var CustomTableCell = import_extension_table_cell.default.extend({
27771
27793
  addAttributes() {
27772
27794
  return {
@@ -27812,6 +27834,46 @@ var CustomTableCell = import_extension_table_cell.default.extend({
27812
27834
  "data-border-width": attributes.borderWidth
27813
27835
  };
27814
27836
  }
27837
+ },
27838
+ cellId: {
27839
+ default: null,
27840
+ parseHTML: (element) => element.getAttribute("data-cell-id") || null,
27841
+ renderHTML: (attributes) => {
27842
+ if (!attributes.cellId) return {};
27843
+ return {
27844
+ "data-cell-id": attributes.cellId
27845
+ };
27846
+ }
27847
+ },
27848
+ numberFormat: {
27849
+ default: null,
27850
+ parseHTML: (element) => element.getAttribute("data-number-format") || null,
27851
+ renderHTML: (attributes) => {
27852
+ if (!attributes.numberFormat) return {};
27853
+ return {
27854
+ "data-number-format": attributes.numberFormat
27855
+ };
27856
+ }
27857
+ },
27858
+ formula: {
27859
+ default: null,
27860
+ parseHTML: (element) => element.getAttribute("data-formula") || null,
27861
+ renderHTML: (attributes) => {
27862
+ if (!attributes.formula) return {};
27863
+ return {
27864
+ "data-formula": attributes.formula
27865
+ };
27866
+ }
27867
+ },
27868
+ computedValue: {
27869
+ default: null,
27870
+ parseHTML: (element) => element.getAttribute("data-computed-value") || null,
27871
+ renderHTML: (attributes) => {
27872
+ if (!attributes.computedValue) return {};
27873
+ return {
27874
+ "data-computed-value": attributes.computedValue
27875
+ };
27876
+ }
27815
27877
  }
27816
27878
  };
27817
27879
  },
@@ -27829,7 +27891,7 @@ var CustomTableCell = import_extension_table_cell.default.extend({
27829
27891
  mergedStyle = mergedStyle.replace(/^;/, "").trim();
27830
27892
  return [
27831
27893
  "td",
27832
- (0, import_core13.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes, mergedStyle ? { style: mergedStyle } : {}),
27894
+ (0, import_core13.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes, getFormulaStateAttributes(HTMLAttributes), mergedStyle ? { style: mergedStyle } : {}),
27833
27895
  0
27834
27896
  ];
27835
27897
  }
@@ -27879,6 +27941,46 @@ var CustomTableHeader = import_extension_table_header.default.extend({
27879
27941
  "data-border-width": attributes.borderWidth
27880
27942
  };
27881
27943
  }
27944
+ },
27945
+ cellId: {
27946
+ default: null,
27947
+ parseHTML: (element) => element.getAttribute("data-cell-id") || null,
27948
+ renderHTML: (attributes) => {
27949
+ if (!attributes.cellId) return {};
27950
+ return {
27951
+ "data-cell-id": attributes.cellId
27952
+ };
27953
+ }
27954
+ },
27955
+ numberFormat: {
27956
+ default: null,
27957
+ parseHTML: (element) => element.getAttribute("data-number-format") || null,
27958
+ renderHTML: (attributes) => {
27959
+ if (!attributes.numberFormat) return {};
27960
+ return {
27961
+ "data-number-format": attributes.numberFormat
27962
+ };
27963
+ }
27964
+ },
27965
+ formula: {
27966
+ default: null,
27967
+ parseHTML: (element) => element.getAttribute("data-formula") || null,
27968
+ renderHTML: (attributes) => {
27969
+ if (!attributes.formula) return {};
27970
+ return {
27971
+ "data-formula": attributes.formula
27972
+ };
27973
+ }
27974
+ },
27975
+ computedValue: {
27976
+ default: null,
27977
+ parseHTML: (element) => element.getAttribute("data-computed-value") || null,
27978
+ renderHTML: (attributes) => {
27979
+ if (!attributes.computedValue) return {};
27980
+ return {
27981
+ "data-computed-value": attributes.computedValue
27982
+ };
27983
+ }
27882
27984
  }
27883
27985
  };
27884
27986
  },
@@ -27896,7 +27998,7 @@ var CustomTableHeader = import_extension_table_header.default.extend({
27896
27998
  mergedStyle = mergedStyle.replace(/^;/, "").trim();
27897
27999
  return [
27898
28000
  "th",
27899
- (0, import_core13.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes, mergedStyle ? { style: mergedStyle } : {}),
28001
+ (0, import_core13.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes, getFormulaStateAttributes(HTMLAttributes), mergedStyle ? { style: mergedStyle } : {}),
27900
28002
  0
27901
28003
  ];
27902
28004
  }
@@ -28591,6 +28693,34 @@ function collectChildren(node) {
28591
28693
  function createEmptyCellNode(cellNode) {
28592
28694
  return cellNode.type.createAndFill(cellNode.attrs) ?? cellNode;
28593
28695
  }
28696
+ function createCellCopyForColumnDuplicate(cellNode) {
28697
+ return cellNode.type.create(cellNode.attrs, cellNode.content);
28698
+ }
28699
+ function getTableRows(tableNode) {
28700
+ const rows = [];
28701
+ tableNode.forEach((rowNode, rowOffset) => {
28702
+ const cells = [];
28703
+ rowNode.forEach((cellNode, cellOffset, index) => {
28704
+ cells.push({
28705
+ index,
28706
+ node: cellNode,
28707
+ relativePos: rowOffset + 1 + cellOffset
28708
+ });
28709
+ });
28710
+ rows.push({
28711
+ node: rowNode,
28712
+ cells
28713
+ });
28714
+ });
28715
+ return rows;
28716
+ }
28717
+ function safeFindCell(map, relativePos) {
28718
+ try {
28719
+ return map.findCell(relativePos);
28720
+ } catch {
28721
+ return null;
28722
+ }
28723
+ }
28594
28724
  function getSelectedTableRect(editor) {
28595
28725
  const cellSelection = getCellSelectionPositions(editor.state.selection);
28596
28726
  if (cellSelection) {
@@ -28716,34 +28846,49 @@ function duplicateTableRowAt(editor, rowIndex, cellPos) {
28716
28846
  }
28717
28847
  function clearTableRowAt(editor, rowIndex, cellPos) {
28718
28848
  return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
28719
- const rows = collectChildren(tableNode);
28720
- const rowNode = rows[rowIndex];
28721
- if (!rowNode) return null;
28722
- const cells = collectChildren(rowNode).map((cellNode) => createEmptyCellNode(cellNode));
28723
- rows[rowIndex] = rowNode.type.create(rowNode.attrs, cells);
28849
+ const map = import_tables.TableMap.get(tableNode);
28850
+ if (rowIndex < 0 || rowIndex >= map.height) return null;
28851
+ const rows = getTableRows(tableNode).map((rowInfo) => {
28852
+ const cells = collectChildren(rowInfo.node);
28853
+ for (const entry of rowInfo.cells) {
28854
+ const rect = safeFindCell(map, entry.relativePos);
28855
+ if (!rect || rect.top > rowIndex || rowIndex >= rect.bottom) continue;
28856
+ cells[entry.index] = createEmptyCellNode(entry.node);
28857
+ }
28858
+ return rowInfo.node.type.create(rowInfo.node.attrs, cells);
28859
+ });
28724
28860
  return tableNode.type.create(tableNode.attrs, rows);
28725
28861
  });
28726
28862
  }
28727
28863
  function duplicateTableColumnAt(editor, columnIndex, cellPos) {
28728
28864
  return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
28729
- const rows = collectChildren(tableNode).map((rowNode) => {
28730
- const cells = collectChildren(rowNode);
28731
- const cellNode = cells[columnIndex];
28732
- if (!cellNode) return rowNode;
28733
- cells.splice(columnIndex + 1, 0, cellNode.copy(cellNode.content));
28734
- return rowNode.type.create(rowNode.attrs, cells);
28865
+ const map = import_tables.TableMap.get(tableNode);
28866
+ if (columnIndex < 0 || columnIndex >= map.width) return null;
28867
+ const rows = getTableRows(tableNode).map((rowInfo, rowIndex) => {
28868
+ const cells = collectChildren(rowInfo.node);
28869
+ const sourceCell = rowInfo.cells.find((entry) => {
28870
+ const rect = safeFindCell(map, entry.relativePos);
28871
+ return rect && rect.top === rowIndex && rect.left <= columnIndex && columnIndex < rect.right;
28872
+ });
28873
+ if (!sourceCell) return rowInfo.node;
28874
+ cells.splice(sourceCell.index + 1, 0, createCellCopyForColumnDuplicate(sourceCell.node));
28875
+ return rowInfo.node.type.create(rowInfo.node.attrs, cells);
28735
28876
  });
28736
28877
  return tableNode.type.create(tableNode.attrs, rows);
28737
28878
  });
28738
28879
  }
28739
28880
  function clearTableColumnAt(editor, columnIndex, cellPos) {
28740
28881
  return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
28741
- const rows = collectChildren(tableNode).map((rowNode) => {
28742
- const cells = collectChildren(rowNode);
28743
- const cellNode = cells[columnIndex];
28744
- if (!cellNode) return rowNode;
28745
- cells[columnIndex] = createEmptyCellNode(cellNode);
28746
- return rowNode.type.create(rowNode.attrs, cells);
28882
+ const map = import_tables.TableMap.get(tableNode);
28883
+ if (columnIndex < 0 || columnIndex >= map.width) return null;
28884
+ const rows = getTableRows(tableNode).map((rowInfo) => {
28885
+ const cells = collectChildren(rowInfo.node);
28886
+ for (const entry of rowInfo.cells) {
28887
+ const rect = safeFindCell(map, entry.relativePos);
28888
+ if (!rect || rect.left > columnIndex || columnIndex >= rect.right) continue;
28889
+ cells[entry.index] = createEmptyCellNode(entry.node);
28890
+ }
28891
+ return rowInfo.node.type.create(rowInfo.node.attrs, cells);
28747
28892
  });
28748
28893
  return tableNode.type.create(tableNode.attrs, rows);
28749
28894
  });
@@ -29795,14 +29940,620 @@ var EditorToolbar = ({
29795
29940
  // src/components/UEditor/menus.tsx
29796
29941
  var import_react62 = require("react");
29797
29942
  var import_react63 = require("@tiptap/react");
29798
- var import_tables2 = require("@tiptap/pm/tables");
29943
+ var import_tables3 = require("@tiptap/pm/tables");
29799
29944
  var import_react_dom8 = require("react-dom");
29800
29945
  var import_lucide_react49 = require("lucide-react");
29946
+
29947
+ // src/components/UEditor/table-formula-commands.ts
29948
+ var import_tables2 = require("@tiptap/pm/tables");
29949
+
29950
+ // src/components/UEditor/table-formula.ts
29951
+ var CELL_ADDRESS_RE = /^([A-Z]+)([1-9]\d*)$/i;
29952
+ var CELL_RANGE_RE = /^([A-Z]+[1-9]\d*):([A-Z]+[1-9]\d*)$/i;
29953
+ var SUPPORTED_FUNCTIONS = /* @__PURE__ */ new Set(["SUM", "AVG", "MIN", "MAX", "COUNT"]);
29954
+ function columnNameToIndex(columnName) {
29955
+ const normalized = columnName.trim().toUpperCase();
29956
+ if (!/^[A-Z]+$/.test(normalized)) {
29957
+ return -1;
29958
+ }
29959
+ let index = 0;
29960
+ for (const char of normalized) {
29961
+ index = index * 26 + char.charCodeAt(0) - 64;
29962
+ }
29963
+ return index - 1;
29964
+ }
29965
+ function indexToColumnName(index) {
29966
+ if (!Number.isInteger(index) || index < 0) {
29967
+ return "";
29968
+ }
29969
+ let value = index + 1;
29970
+ let name = "";
29971
+ while (value > 0) {
29972
+ const remainder = (value - 1) % 26;
29973
+ name = String.fromCharCode(65 + remainder) + name;
29974
+ value = Math.floor((value - 1) / 26);
29975
+ }
29976
+ return name;
29977
+ }
29978
+ function parseTableCellAddress(input) {
29979
+ const match = input.trim().match(CELL_ADDRESS_RE);
29980
+ if (!match) {
29981
+ return null;
29982
+ }
29983
+ const column = columnNameToIndex(match[1] ?? "");
29984
+ const row = Number.parseInt(match[2] ?? "", 10) - 1;
29985
+ if (column < 0 || row < 0) {
29986
+ return null;
29987
+ }
29988
+ return {
29989
+ column,
29990
+ row,
29991
+ label: `${indexToColumnName(column)}${row + 1}`
29992
+ };
29993
+ }
29994
+ function parseTableCellRange(input) {
29995
+ const match = input.trim().match(CELL_RANGE_RE);
29996
+ if (!match) {
29997
+ return null;
29998
+ }
29999
+ const from = parseTableCellAddress(match[1] ?? "");
30000
+ const to = parseTableCellAddress(match[2] ?? "");
30001
+ if (!from || !to) {
30002
+ return null;
30003
+ }
30004
+ return { from, to };
30005
+ }
30006
+ function getTableCellRangeLabels(range) {
30007
+ const startColumn = Math.min(range.from.column, range.to.column);
30008
+ const endColumn = Math.max(range.from.column, range.to.column);
30009
+ const startRow = Math.min(range.from.row, range.to.row);
30010
+ const endRow = Math.max(range.from.row, range.to.row);
30011
+ const labels = [];
30012
+ for (let row = startRow; row <= endRow; row += 1) {
30013
+ for (let column = startColumn; column <= endColumn; column += 1) {
30014
+ labels.push(`${indexToColumnName(column)}${row + 1}`);
30015
+ }
30016
+ }
30017
+ return labels;
30018
+ }
30019
+ function normalizeTableFormula(formula) {
30020
+ return formula.trim().replace(/^=/, "").trim();
30021
+ }
30022
+ function formatFormulaError(error) {
30023
+ return `#${error.toUpperCase()}`;
30024
+ }
30025
+ function getTableFormulaReferences(formula) {
30026
+ const normalized = normalizeTableFormula(formula);
30027
+ if (!normalized) return [];
30028
+ const tokens = tokenizeFormula(normalized);
30029
+ if (!tokens) return [];
30030
+ const references = /* @__PURE__ */ new Set();
30031
+ for (const token of tokens) {
30032
+ if (token.type === "cell") {
30033
+ references.add(token.value);
30034
+ } else if (token.type === "range") {
30035
+ const range = parseTableCellRange(token.value);
30036
+ if (!range) continue;
30037
+ for (const label of getTableCellRangeLabels(range)) {
30038
+ references.add(label);
30039
+ }
30040
+ }
30041
+ }
30042
+ return Array.from(references);
30043
+ }
30044
+ function buildTableFormulaDependencyGraph(cells) {
30045
+ const dependencies = /* @__PURE__ */ new Map();
30046
+ const dependents = /* @__PURE__ */ new Map();
30047
+ const formulas = /* @__PURE__ */ new Map();
30048
+ for (const cell of cells) {
30049
+ const label = cell.label.toUpperCase();
30050
+ formulas.set(label, cell.formula);
30051
+ dependencies.set(label, new Set(getTableFormulaReferences(cell.formula)));
30052
+ if (!dependents.has(label)) {
30053
+ dependents.set(label, /* @__PURE__ */ new Set());
30054
+ }
30055
+ }
30056
+ for (const [label, refs] of dependencies) {
30057
+ for (const ref of refs) {
30058
+ if (!dependents.has(ref)) {
30059
+ dependents.set(ref, /* @__PURE__ */ new Set());
30060
+ }
30061
+ dependents.get(ref)?.add(label);
30062
+ }
30063
+ }
30064
+ return { dependencies, dependents, formulas };
30065
+ }
30066
+ function getTableFormulaCircularReferences(graph) {
30067
+ const visiting = /* @__PURE__ */ new Set();
30068
+ const visited = /* @__PURE__ */ new Set();
30069
+ const circular = /* @__PURE__ */ new Set();
30070
+ const stack = [];
30071
+ const visit = (label) => {
30072
+ if (visiting.has(label)) {
30073
+ const start = stack.indexOf(label);
30074
+ for (const cycleLabel of start >= 0 ? stack.slice(start) : [label]) {
30075
+ circular.add(cycleLabel);
30076
+ }
30077
+ return;
30078
+ }
30079
+ if (visited.has(label)) return;
30080
+ visiting.add(label);
30081
+ stack.push(label);
30082
+ for (const ref of graph.dependencies.get(label) ?? []) {
30083
+ if (graph.formulas.has(ref)) {
30084
+ visit(ref);
30085
+ }
30086
+ }
30087
+ stack.pop();
30088
+ visiting.delete(label);
30089
+ visited.add(label);
30090
+ };
30091
+ for (const label of graph.formulas.keys()) {
30092
+ visit(label);
30093
+ }
30094
+ return circular;
30095
+ }
30096
+ function getTableFormulaRecalculationOrder(graph) {
30097
+ const circular = getTableFormulaCircularReferences(graph);
30098
+ const visited = /* @__PURE__ */ new Set();
30099
+ const order = [];
30100
+ const visit = (label) => {
30101
+ if (visited.has(label) || circular.has(label)) return;
30102
+ visited.add(label);
30103
+ for (const ref of graph.dependencies.get(label) ?? []) {
30104
+ if (graph.formulas.has(ref)) {
30105
+ visit(ref);
30106
+ }
30107
+ }
30108
+ order.push(label);
30109
+ };
30110
+ for (const label of graph.formulas.keys()) {
30111
+ visit(label);
30112
+ }
30113
+ return { order, circular };
30114
+ }
30115
+ function evaluateBasicTableFormula(formula, getCellValue) {
30116
+ const normalized = normalizeTableFormula(formula);
30117
+ if (!normalized) {
30118
+ return { value: null, error: "empty" };
30119
+ }
30120
+ const tokens = tokenizeFormula(normalized);
30121
+ if (!tokens) {
30122
+ return { value: null, error: "invalid-formula" };
30123
+ }
30124
+ const parser = new FormulaParser(tokens, getCellValue);
30125
+ const result = parser.parseExpression();
30126
+ if (result.error) {
30127
+ return result;
30128
+ }
30129
+ if (!parser.isComplete()) {
30130
+ return { value: null, error: "invalid-formula" };
30131
+ }
30132
+ return result;
30133
+ }
30134
+ function tokenizeFormula(formula) {
30135
+ const tokens = [];
30136
+ let index = 0;
30137
+ while (index < formula.length) {
30138
+ const char = formula[index];
30139
+ if (!char) break;
30140
+ if (/\s/.test(char)) {
30141
+ index += 1;
30142
+ continue;
30143
+ }
30144
+ if (char === "," || char === "+" || char === "-" || char === "*" || char === "/" || char === "(" || char === ")") {
30145
+ if (char === ",") tokens.push({ type: "comma", value: char });
30146
+ else if (char === "(" || char === ")") tokens.push({ type: "paren", value: char });
30147
+ else tokens.push({ type: "operator", value: char });
30148
+ index += 1;
30149
+ continue;
30150
+ }
30151
+ const numberMatch = formula.slice(index).match(/^\d+(?:\.\d+)?/);
30152
+ if (numberMatch?.[0]) {
30153
+ tokens.push({ type: "number", value: Number.parseFloat(numberMatch[0]) });
30154
+ index += numberMatch[0].length;
30155
+ continue;
30156
+ }
30157
+ const identifierMatch = formula.slice(index).match(/^[A-Z]+[1-9]\d*(?::[A-Z]+[1-9]\d*)?|^[A-Z]+/i);
30158
+ if (identifierMatch?.[0]) {
30159
+ const value = identifierMatch[0].toUpperCase();
30160
+ if (CELL_RANGE_RE.test(value)) tokens.push({ type: "range", value });
30161
+ else if (parseTableCellAddress(value)) tokens.push({ type: "cell", value });
30162
+ else if (SUPPORTED_FUNCTIONS.has(value)) tokens.push({ type: "function", value });
30163
+ else return null;
30164
+ index += identifierMatch[0].length;
30165
+ continue;
30166
+ }
30167
+ return null;
30168
+ }
30169
+ return tokens;
30170
+ }
30171
+ var FormulaParser = class {
30172
+ constructor(tokens, getCellValue) {
30173
+ this.tokens = tokens;
30174
+ this.getCellValue = getCellValue;
30175
+ this.index = 0;
30176
+ }
30177
+ isComplete() {
30178
+ return this.index >= this.tokens.length;
30179
+ }
30180
+ parseExpression() {
30181
+ let left = this.parseTerm();
30182
+ while (!left.error) {
30183
+ const operator = this.peekOperator(["+", "-"]);
30184
+ if (!operator) break;
30185
+ this.index += 1;
30186
+ const right = this.parseTerm();
30187
+ if (right.error) return right;
30188
+ left = {
30189
+ value: operator.value === "+" ? left.value + right.value : left.value - right.value,
30190
+ error: null
30191
+ };
30192
+ }
30193
+ return left;
30194
+ }
30195
+ parseTerm() {
30196
+ let left = this.parseFactor();
30197
+ while (!left.error) {
30198
+ const operator = this.peekOperator(["*", "/"]);
30199
+ if (!operator) break;
30200
+ this.index += 1;
30201
+ const right = this.parseFactor();
30202
+ if (right.error) return right;
30203
+ if (operator.value === "/" && right.value === 0) {
30204
+ return { value: null, error: "division-by-zero" };
30205
+ }
30206
+ left = {
30207
+ value: operator.value === "*" ? left.value * right.value : left.value / right.value,
30208
+ error: null
30209
+ };
30210
+ }
30211
+ return left;
30212
+ }
30213
+ parseFactor() {
30214
+ const token = this.tokens[this.index];
30215
+ if (!token) {
30216
+ return { value: null, error: "invalid-formula" };
30217
+ }
30218
+ if (token.type === "operator" && token.value === "-") {
30219
+ this.index += 1;
30220
+ const value = this.parseFactor();
30221
+ if (value.error) return value;
30222
+ return { value: -value.value, error: null };
30223
+ }
30224
+ if (token.type === "number") {
30225
+ this.index += 1;
30226
+ return { value: token.value, error: null };
30227
+ }
30228
+ if (token.type === "cell") {
30229
+ this.index += 1;
30230
+ return this.readCellNumber(token.value);
30231
+ }
30232
+ if (token.type === "function") {
30233
+ return this.parseFunction(token.value);
30234
+ }
30235
+ if (token.type === "paren" && token.value === "(") {
30236
+ this.index += 1;
30237
+ const value = this.parseExpression();
30238
+ if (value.error) return value;
30239
+ if (!this.consumeParen(")")) {
30240
+ return { value: null, error: "invalid-formula" };
30241
+ }
30242
+ return value;
30243
+ }
30244
+ return { value: null, error: "invalid-formula" };
30245
+ }
30246
+ parseFunction(name) {
30247
+ this.index += 1;
30248
+ if (!this.consumeParen("(")) {
30249
+ return { value: null, error: "invalid-formula" };
30250
+ }
30251
+ const values = [];
30252
+ while (true) {
30253
+ const token = this.tokens[this.index];
30254
+ if (!token) {
30255
+ return { value: null, error: "invalid-formula" };
30256
+ }
30257
+ if (token.type === "range") {
30258
+ this.index += 1;
30259
+ const range = parseTableCellRange(token.value);
30260
+ if (!range) return { value: null, error: "invalid-reference" };
30261
+ for (const label of getTableCellRangeLabels(range)) {
30262
+ const cellValue = this.readCellNumber(label);
30263
+ if (cellValue.error) return cellValue;
30264
+ values.push(cellValue.value);
30265
+ }
30266
+ } else {
30267
+ const value = this.parseExpression();
30268
+ if (value.error) return value;
30269
+ values.push(value.value);
30270
+ }
30271
+ if (this.consumeComma()) {
30272
+ continue;
30273
+ }
30274
+ if (this.consumeParen(")")) {
30275
+ break;
30276
+ }
30277
+ return { value: null, error: "invalid-formula" };
30278
+ }
30279
+ if (values.length === 0) {
30280
+ return { value: null, error: "invalid-formula" };
30281
+ }
30282
+ if (name === "SUM") return { value: values.reduce((sum, value) => sum + value, 0), error: null };
30283
+ if (name === "AVG") return { value: values.reduce((sum, value) => sum + value, 0) / values.length, error: null };
30284
+ if (name === "MIN") return { value: Math.min(...values), error: null };
30285
+ if (name === "MAX") return { value: Math.max(...values), error: null };
30286
+ if (name === "COUNT") return { value: values.length, error: null };
30287
+ return { value: null, error: "invalid-formula" };
30288
+ }
30289
+ readCellNumber(label) {
30290
+ const value = this.getCellValue(label);
30291
+ const parsed = typeof value === "number" ? value : Number.parseFloat(String(value ?? "").trim());
30292
+ if (!Number.isFinite(parsed)) {
30293
+ return { value: null, error: "invalid-reference" };
30294
+ }
30295
+ return { value: parsed, error: null };
30296
+ }
30297
+ peekOperator(operators) {
30298
+ const token = this.tokens[this.index];
30299
+ return token?.type === "operator" && operators.includes(token.value) ? token : null;
30300
+ }
30301
+ consumeComma() {
30302
+ if (this.tokens[this.index]?.type !== "comma") {
30303
+ return false;
30304
+ }
30305
+ this.index += 1;
30306
+ return true;
30307
+ }
30308
+ consumeParen(value) {
30309
+ const token = this.tokens[this.index];
30310
+ if (token?.type !== "paren" || token.value !== value) {
30311
+ return false;
30312
+ }
30313
+ this.index += 1;
30314
+ return true;
30315
+ }
30316
+ };
30317
+
30318
+ // src/components/UEditor/table-formula-commands.ts
30319
+ var UEDITOR_TABLE_FORMULA_RECALCULATE_META = "ueditorTableFormulaRecalculate";
30320
+ function collectChildren2(node) {
30321
+ const children = [];
30322
+ node.forEach((child) => children.push(child));
30323
+ return children;
30324
+ }
30325
+ function getTableRows2(tableNode) {
30326
+ const rows = [];
30327
+ tableNode.forEach((rowNode, rowOffset) => {
30328
+ const cells = [];
30329
+ rowNode.forEach((cellNode, cellOffset, index) => {
30330
+ cells.push({
30331
+ index,
30332
+ node: cellNode,
30333
+ relativePos: rowOffset + 1 + cellOffset
30334
+ });
30335
+ });
30336
+ rows.push({ node: rowNode, cells });
30337
+ });
30338
+ return rows;
30339
+ }
30340
+ function safeFindCell2(map, relativePos) {
30341
+ try {
30342
+ return map.findCell(relativePos);
30343
+ } catch {
30344
+ return null;
30345
+ }
30346
+ }
30347
+ function getCellText(cellNode) {
30348
+ return cellNode.textBetween(0, cellNode.content.size, "\n").trim();
30349
+ }
30350
+ function createCellDisplayContent(cellNode, displayValue) {
30351
+ const paragraphType = cellNode.type.schema.nodes.paragraph;
30352
+ if (!paragraphType || !displayValue) {
30353
+ return paragraphType ? [paragraphType.create()] : cellNode.content;
30354
+ }
30355
+ return [paragraphType.create(null, cellNode.type.schema.text(displayValue))];
30356
+ }
30357
+ function buildTableValueGetter(tableNode) {
30358
+ const map = import_tables2.TableMap.get(tableNode);
30359
+ const values = /* @__PURE__ */ new Map();
30360
+ for (const rowInfo of getTableRows2(tableNode)) {
30361
+ for (const entry of rowInfo.cells) {
30362
+ const rect = safeFindCell2(map, entry.relativePos);
30363
+ if (!rect) continue;
30364
+ const label = `${indexToColumnName(rect.left)}${rect.top + 1}`;
30365
+ const computedValue = entry.node.attrs.computedValue;
30366
+ values.set(label, typeof computedValue === "string" && computedValue.trim() ? computedValue : getCellText(entry.node));
30367
+ }
30368
+ }
30369
+ return (label) => values.get(label.toUpperCase());
30370
+ }
30371
+ function buildTableValueMap(tableNode) {
30372
+ const map = import_tables2.TableMap.get(tableNode);
30373
+ const values = /* @__PURE__ */ new Map();
30374
+ for (const rowInfo of getTableRows2(tableNode)) {
30375
+ for (const entry of rowInfo.cells) {
30376
+ const rect = safeFindCell2(map, entry.relativePos);
30377
+ if (!rect) continue;
30378
+ const label = `${indexToColumnName(rect.left)}${rect.top + 1}`;
30379
+ const computedValue = entry.node.attrs.computedValue;
30380
+ values.set(label, typeof computedValue === "string" && computedValue.trim() ? computedValue : getCellText(entry.node));
30381
+ }
30382
+ }
30383
+ return values;
30384
+ }
30385
+ function getFormulaComputedValue(formula, tableNode) {
30386
+ const result = evaluateBasicTableFormula(formula, buildTableValueGetter(tableNode));
30387
+ return result.error ? `#${result.error.toUpperCase()}` : String(result.value);
30388
+ }
30389
+ function normalizeFormulaInput(formula) {
30390
+ const trimmed = formula.trim();
30391
+ if (!trimmed) return "";
30392
+ return trimmed.startsWith("=") ? trimmed : `=${trimmed}`;
30393
+ }
30394
+ function setSelectedTableCellFormula(editor, formula) {
30395
+ const normalized = normalizeFormulaInput(formula);
30396
+ const { state, view } = editor;
30397
+ if (!normalized) {
30398
+ const clearedFormula = (0, import_tables2.setCellAttr)("formula", null)(state, view.dispatch.bind(view));
30399
+ const clearedValue = (0, import_tables2.setCellAttr)("computedValue", null)(editor.state, view.dispatch.bind(view));
30400
+ if (clearedFormula || clearedValue) {
30401
+ view.focus();
30402
+ dispatchTableLayoutChange(editor);
30403
+ return true;
30404
+ }
30405
+ return false;
30406
+ }
30407
+ const rect = (0, import_tables2.selectedRect)(state);
30408
+ const computedValue = getFormulaComputedValue(normalized, rect.table);
30409
+ const appliedFormula = (0, import_tables2.setCellAttr)("formula", normalized)(state, view.dispatch.bind(view));
30410
+ const appliedValue = (0, import_tables2.setCellAttr)("computedValue", computedValue)(editor.state, view.dispatch.bind(view));
30411
+ if (appliedFormula || appliedValue) {
30412
+ view.focus();
30413
+ dispatchTableLayoutChange(editor);
30414
+ return true;
30415
+ }
30416
+ return false;
30417
+ }
30418
+ function clearSelectedTableCellFormula(editor) {
30419
+ return setSelectedTableCellFormula(editor, "");
30420
+ }
30421
+ function promoteFormulaTextInTableNode(tableNode) {
30422
+ let changed = false;
30423
+ const rows = getTableRows2(tableNode).map((rowInfo) => {
30424
+ const cells = collectChildren2(rowInfo.node);
30425
+ for (const entry of rowInfo.cells) {
30426
+ const text = getCellText(entry.node);
30427
+ const formula = text.startsWith("=") ? normalizeFormulaInput(text) : "";
30428
+ if (!formula || entry.node.attrs.formula === formula) continue;
30429
+ cells[entry.index] = entry.node.type.create(
30430
+ {
30431
+ ...entry.node.attrs,
30432
+ formula,
30433
+ computedValue: null
30434
+ },
30435
+ entry.node.content,
30436
+ entry.node.marks
30437
+ );
30438
+ changed = true;
30439
+ }
30440
+ return rowInfo.node.type.create(rowInfo.node.attrs, cells);
30441
+ });
30442
+ return {
30443
+ tableNode: changed ? tableNode.type.create(tableNode.attrs, rows) : tableNode,
30444
+ changed
30445
+ };
30446
+ }
30447
+ function recalculateTableNode(tableNode) {
30448
+ const promoted = promoteFormulaTextInTableNode(tableNode);
30449
+ tableNode = promoted.tableNode;
30450
+ const map = import_tables2.TableMap.get(tableNode);
30451
+ const values = buildTableValueMap(tableNode);
30452
+ const formulaEntries = /* @__PURE__ */ new Map();
30453
+ let changed = false;
30454
+ const rowInfos = getTableRows2(tableNode);
30455
+ for (const [rowIndex, rowInfo] of rowInfos.entries()) {
30456
+ for (const entry of rowInfo.cells) {
30457
+ const formula = typeof entry.node.attrs.formula === "string" ? entry.node.attrs.formula.trim() : "";
30458
+ if (!formula) continue;
30459
+ const rectForCell = safeFindCell2(map, entry.relativePos);
30460
+ if (!rectForCell) continue;
30461
+ const label = `${indexToColumnName(rectForCell.left)}${rectForCell.top + 1}`;
30462
+ formulaEntries.set(label, {
30463
+ rowIndex,
30464
+ cellIndex: entry.index,
30465
+ node: entry.node,
30466
+ formula
30467
+ });
30468
+ }
30469
+ }
30470
+ const graph = buildTableFormulaDependencyGraph(
30471
+ Array.from(formulaEntries, ([label, entry]) => ({
30472
+ label,
30473
+ formula: entry.formula
30474
+ }))
30475
+ );
30476
+ const { order, circular } = getTableFormulaRecalculationOrder(graph);
30477
+ const computedValues = /* @__PURE__ */ new Map();
30478
+ const getCellValue = (label) => values.get(label.toUpperCase());
30479
+ for (const label of circular) {
30480
+ computedValues.set(label, formatFormulaError("circular-reference"));
30481
+ values.set(label, formatFormulaError("circular-reference"));
30482
+ }
30483
+ for (const label of order) {
30484
+ const entry = formulaEntries.get(label);
30485
+ if (!entry) continue;
30486
+ const result = evaluateBasicTableFormula(entry.formula, getCellValue);
30487
+ const computedValue = result.error ? formatFormulaError(result.error) : String(result.value);
30488
+ computedValues.set(label, computedValue);
30489
+ values.set(label, computedValue);
30490
+ }
30491
+ const rows = rowInfos.map((rowInfo) => {
30492
+ const cells = collectChildren2(rowInfo.node);
30493
+ for (const entry of rowInfo.cells) {
30494
+ const rectForCell = safeFindCell2(map, entry.relativePos);
30495
+ if (!rectForCell) continue;
30496
+ const label = `${indexToColumnName(rectForCell.left)}${rectForCell.top + 1}`;
30497
+ const computedValue = computedValues.get(label);
30498
+ if (computedValue == null) continue;
30499
+ const contentMatchesComputedValue = getCellText(entry.node) === computedValue;
30500
+ if (entry.node.attrs.computedValue === computedValue && contentMatchesComputedValue) continue;
30501
+ cells[entry.index] = entry.node.type.create(
30502
+ {
30503
+ ...entry.node.attrs,
30504
+ computedValue
30505
+ },
30506
+ createCellDisplayContent(entry.node, computedValue),
30507
+ entry.node.marks
30508
+ );
30509
+ changed = true;
30510
+ }
30511
+ return rowInfo.node.type.create(rowInfo.node.attrs, cells);
30512
+ });
30513
+ if (!changed) return promoted.changed ? tableNode : null;
30514
+ return tableNode.type.create(tableNode.attrs, rows);
30515
+ }
30516
+ function recalculateSelectedTable(editor) {
30517
+ const rect = (0, import_tables2.selectedRect)(editor.state);
30518
+ const tableNode = rect.table;
30519
+ const nextTable = recalculateTableNode(tableNode);
30520
+ if (!nextTable) return false;
30521
+ editor.view.dispatch(
30522
+ editor.state.tr.replaceWith(rect.tableStart - 1, rect.tableStart - 1 + tableNode.nodeSize, nextTable).setMeta(UEDITOR_TABLE_FORMULA_RECALCULATE_META, true)
30523
+ );
30524
+ dispatchTableLayoutChange(editor);
30525
+ return true;
30526
+ }
30527
+ function recalculateAllTableFormulas(editor) {
30528
+ const replacements = [];
30529
+ editor.state.doc.descendants((node, pos) => {
30530
+ if (node.type.name !== "table") return true;
30531
+ const nextTable = recalculateTableNode(node);
30532
+ if (nextTable) {
30533
+ replacements.push({
30534
+ from: pos,
30535
+ to: pos + node.nodeSize,
30536
+ node: nextTable
30537
+ });
30538
+ }
30539
+ return false;
30540
+ });
30541
+ if (replacements.length === 0) return false;
30542
+ let tr = editor.state.tr;
30543
+ for (const replacement of replacements.reverse()) {
30544
+ tr = tr.replaceWith(replacement.from, replacement.to, replacement.node);
30545
+ }
30546
+ editor.view.dispatch(tr.setMeta(UEDITOR_TABLE_FORMULA_RECALCULATE_META, true));
30547
+ dispatchTableLayoutChange(editor);
30548
+ return true;
30549
+ }
30550
+
30551
+ // src/components/UEditor/menus.tsx
29801
30552
  var import_jsx_runtime86 = require("react/jsx-runtime");
29802
30553
  function applyTableCellBackground(editor, color) {
29803
30554
  const value = color || null;
29804
30555
  const { state, view } = editor;
29805
- const applied = (0, import_tables2.setCellAttr)("backgroundColor", value)(state, view.dispatch.bind(view));
30556
+ const applied = (0, import_tables3.setCellAttr)("backgroundColor", value)(state, view.dispatch.bind(view));
29806
30557
  if (applied) {
29807
30558
  view.focus();
29808
30559
  return;
@@ -29828,6 +30579,8 @@ var BubbleMenuContent = ({
29828
30579
  setShowLinkInput(initialShowLinkInput);
29829
30580
  }, [initialShowLinkInput]);
29830
30581
  const [showTypographyPanel, setShowTypographyPanel] = (0, import_react62.useState)(false);
30582
+ const [showFormulaPanel, setShowFormulaPanel] = (0, import_react62.useState)(false);
30583
+ const [formulaDraft, setFormulaDraft] = (0, import_react62.useState)("");
29831
30584
  const [showFontSizeOptions, setShowFontSizeOptions] = (0, import_react62.useState)(false);
29832
30585
  const [fontSizeDraft, setFontSizeDraft] = (0, import_react62.useState)("");
29833
30586
  const isImageSelected = editor.isActive("image");
@@ -29838,7 +30591,8 @@ var BubbleMenuContent = ({
29838
30591
  const currentTextColor = normalizeStyleValue(textStyleAttrs.color) || "inherit";
29839
30592
  const currentHighlightColor = normalizeStyleValue(editor.getAttributes("highlight").color) || "";
29840
30593
  const currentCellBgColor = normalizeStyleValue(editor.getAttributes("tableCell").backgroundColor || editor.getAttributes("tableHeader").backgroundColor) || "";
29841
- const isInTable2 = (0, import_tables2.isInTable)(editor.state);
30594
+ const currentCellFormula = normalizeStyleValue(editor.getAttributes("tableCell").formula || editor.getAttributes("tableHeader").formula) || "";
30595
+ const isInTable2 = (0, import_tables3.isInTable)(editor.state);
29842
30596
  const canMergeCells = isInTable2 && editor.can().mergeCells();
29843
30597
  const canSplitCell = isInTable2 && editor.can().splitCell();
29844
30598
  const currentFontSize = normalizeStyleValue(textStyleAttrs.fontSize);
@@ -29854,6 +30608,9 @@ var BubbleMenuContent = ({
29854
30608
  (0, import_react62.useEffect)(() => {
29855
30609
  setFontSizeDraft(currentFontSize.replace(/px$/i, ""));
29856
30610
  }, [currentFontSize]);
30611
+ (0, import_react62.useEffect)(() => {
30612
+ setFormulaDraft(currentCellFormula);
30613
+ }, [currentCellFormula]);
29857
30614
  const applyFontSizeDraft = () => {
29858
30615
  const normalized = fontSizeDraft.trim();
29859
30616
  if (!normalized) {
@@ -30056,6 +30813,82 @@ var BubbleMenuContent = ({
30056
30813
  )
30057
30814
  ] });
30058
30815
  }
30816
+ if (showFormulaPanel && isInTable2) {
30817
+ return /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "w-72 p-2", children: [
30818
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
30819
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("span", { className: "px-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: t("tableMenu.formula") || "Formula" }),
30820
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
30821
+ "button",
30822
+ {
30823
+ type: "button",
30824
+ onClick: () => {
30825
+ setShowFormulaPanel(false);
30826
+ onKeepOpenChange?.(false);
30827
+ },
30828
+ className: "rounded-md px-2 py-1 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
30829
+ children: t("colors.done")
30830
+ }
30831
+ )
30832
+ ] }),
30833
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("div", { className: "flex h-9 items-center overflow-hidden rounded-md border border-border/60 bg-muted/40", children: /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
30834
+ "input",
30835
+ {
30836
+ value: formulaDraft,
30837
+ onChange: (event) => setFormulaDraft(event.target.value),
30838
+ onMouseDown: (event) => event.stopPropagation(),
30839
+ onClick: (event) => event.stopPropagation(),
30840
+ onKeyDown: (event) => {
30841
+ event.stopPropagation();
30842
+ if (event.key === "Enter") {
30843
+ event.preventDefault();
30844
+ setSelectedTableCellFormula(editor, formulaDraft);
30845
+ setShowFormulaPanel(false);
30846
+ onKeepOpenChange?.(false);
30847
+ }
30848
+ },
30849
+ "aria-label": t("tableMenu.formula") || "Formula",
30850
+ placeholder: "=SUM(A1:A3)",
30851
+ className: "h-full min-w-0 flex-1 bg-transparent px-2 text-sm font-medium text-foreground outline-none"
30852
+ }
30853
+ ) }),
30854
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "mt-2 grid grid-cols-3 gap-1", children: [
30855
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
30856
+ "button",
30857
+ {
30858
+ type: "button",
30859
+ onClick: () => {
30860
+ setSelectedTableCellFormula(editor, formulaDraft);
30861
+ setShowFormulaPanel(false);
30862
+ onKeepOpenChange?.(false);
30863
+ },
30864
+ className: "h-8 rounded-md bg-primary/10 text-xs font-semibold text-primary transition-colors hover:bg-primary/15",
30865
+ children: t("tableMenu.apply") || "Apply"
30866
+ }
30867
+ ),
30868
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
30869
+ "button",
30870
+ {
30871
+ type: "button",
30872
+ onClick: () => {
30873
+ clearSelectedTableCellFormula(editor);
30874
+ setFormulaDraft("");
30875
+ },
30876
+ className: "h-8 rounded-md bg-muted/40 text-xs font-semibold text-foreground transition-colors hover:bg-muted",
30877
+ children: t("tableMenu.clear") || "Clear"
30878
+ }
30879
+ ),
30880
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
30881
+ "button",
30882
+ {
30883
+ type: "button",
30884
+ onClick: () => recalculateSelectedTable(editor),
30885
+ className: "h-8 rounded-md bg-muted/40 text-xs font-semibold text-foreground transition-colors hover:bg-muted",
30886
+ children: t("tableMenu.recalculate") || "Recalc"
30887
+ }
30888
+ )
30889
+ ] })
30890
+ ] });
30891
+ }
30059
30892
  if (showTypographyPanel) {
30060
30893
  return /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "w-72 p-2", children: [
30061
30894
  /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
@@ -30269,6 +31102,21 @@ var BubbleMenuContent = ({
30269
31102
  )
30270
31103
  }
30271
31104
  ),
31105
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
31106
+ ToolbarButton,
31107
+ {
31108
+ onMouseDown: () => {
31109
+ onKeepOpenChange?.(true);
31110
+ },
31111
+ onClick: () => {
31112
+ setFormulaDraft(currentCellFormula);
31113
+ setShowFormulaPanel(true);
31114
+ },
31115
+ active: Boolean(currentCellFormula),
31116
+ title: t("tableMenu.formula") || "Formula",
31117
+ children: /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(import_lucide_react49.Sigma, { className: "w-4 h-4" })
31118
+ }
31119
+ ),
30272
31120
  /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
30273
31121
  ToolbarButton,
30274
31122
  {
@@ -34110,7 +34958,7 @@ if (typeof WeakMap != "undefined") {
34110
34958
  return cache[cachePos++] = value;
34111
34959
  };
34112
34960
  }
34113
- var TableMap2 = class {
34961
+ var TableMap3 = class {
34114
34962
  constructor(width, height, map, problems) {
34115
34963
  this.width = width;
34116
34964
  this.height = height;
@@ -34247,7 +35095,7 @@ function computeMap(table) {
34247
35095
  pos++;
34248
35096
  }
34249
35097
  if (width === 0 || height === 0) (problems || (problems = [])).push({ type: "zero_sized" });
34250
- const tableMap = new TableMap2(width, height, map, problems);
35098
+ const tableMap = new TableMap3(width, height, map, problems);
34251
35099
  let badWidths = false;
34252
35100
  for (let i = 0; !badWidths && i < colWidths.length; i += 2) if (colWidths[i] != null && colWidths[i + 1] < height) badWidths = true;
34253
35101
  if (badWidths) findBadColWidths(tableMap, colWidths, table);
@@ -34351,7 +35199,7 @@ function inSameTable($cellA, $cellB) {
34351
35199
  }
34352
35200
  function nextCell($pos, axis, dir) {
34353
35201
  const table = $pos.node(-1);
34354
- const map = TableMap2.get(table);
35202
+ const map = TableMap3.get(table);
34355
35203
  const tableStart = $pos.start(-1);
34356
35204
  const moved = map.nextCell($pos.pos - tableStart, axis, dir);
34357
35205
  return moved == null ? null : $pos.node(0).resolve(tableStart + moved);
@@ -34371,7 +35219,7 @@ function removeColSpan(attrs, pos, n = 1) {
34371
35219
  var CellSelection = class CellSelection2 extends Selection {
34372
35220
  constructor($anchorCell, $headCell = $anchorCell) {
34373
35221
  const table = $anchorCell.node(-1);
34374
- const map = TableMap2.get(table);
35222
+ const map = TableMap3.get(table);
34375
35223
  const tableStart = $anchorCell.start(-1);
34376
35224
  const rect = map.rectBetween($anchorCell.pos - tableStart, $headCell.pos - tableStart);
34377
35225
  const doc = $anchorCell.node(0);
@@ -34400,7 +35248,7 @@ var CellSelection = class CellSelection2 extends Selection {
34400
35248
  }
34401
35249
  content() {
34402
35250
  const table = this.$anchorCell.node(-1);
34403
- const map = TableMap2.get(table);
35251
+ const map = TableMap3.get(table);
34404
35252
  const tableStart = this.$anchorCell.start(-1);
34405
35253
  const rect = map.rectBetween(this.$anchorCell.pos - tableStart, this.$headCell.pos - tableStart);
34406
35254
  const seen = {};
@@ -34454,7 +35302,7 @@ var CellSelection = class CellSelection2 extends Selection {
34454
35302
  }
34455
35303
  forEachCell(f) {
34456
35304
  const table = this.$anchorCell.node(-1);
34457
- const map = TableMap2.get(table);
35305
+ const map = TableMap3.get(table);
34458
35306
  const tableStart = this.$anchorCell.start(-1);
34459
35307
  const cells = map.cellsInRect(map.rectBetween(this.$anchorCell.pos - tableStart, this.$headCell.pos - tableStart));
34460
35308
  for (let i = 0; i < cells.length; i++) f(table.nodeAt(cells[i]), tableStart + cells[i]);
@@ -34469,7 +35317,7 @@ var CellSelection = class CellSelection2 extends Selection {
34469
35317
  }
34470
35318
  static colSelection($anchorCell, $headCell = $anchorCell) {
34471
35319
  const table = $anchorCell.node(-1);
34472
- const map = TableMap2.get(table);
35320
+ const map = TableMap3.get(table);
34473
35321
  const tableStart = $anchorCell.start(-1);
34474
35322
  const anchorRect = map.findCell($anchorCell.pos - tableStart);
34475
35323
  const headRect = map.findCell($headCell.pos - tableStart);
@@ -34485,7 +35333,7 @@ var CellSelection = class CellSelection2 extends Selection {
34485
35333
  }
34486
35334
  isRowSelection() {
34487
35335
  const table = this.$anchorCell.node(-1);
34488
- const map = TableMap2.get(table);
35336
+ const map = TableMap3.get(table);
34489
35337
  const tableStart = this.$anchorCell.start(-1);
34490
35338
  const anchorLeft = map.colCount(this.$anchorCell.pos - tableStart);
34491
35339
  const headLeft = map.colCount(this.$headCell.pos - tableStart);
@@ -34499,7 +35347,7 @@ var CellSelection = class CellSelection2 extends Selection {
34499
35347
  }
34500
35348
  static rowSelection($anchorCell, $headCell = $anchorCell) {
34501
35349
  const table = $anchorCell.node(-1);
34502
- const map = TableMap2.get(table);
35350
+ const map = TableMap3.get(table);
34503
35351
  const tableStart = $anchorCell.start(-1);
34504
35352
  const anchorRect = map.findCell($anchorCell.pos - tableStart);
34505
35353
  const headRect = map.findCell($headCell.pos - tableStart);
@@ -34548,7 +35396,7 @@ var CellBookmark = class CellBookmark2 {
34548
35396
  };
34549
35397
  var fixTablesKey = new PluginKey4("fix-tables");
34550
35398
  function convertTableNodeToArrayOfRows(tableNode) {
34551
- const map = TableMap2.get(tableNode);
35399
+ const map = TableMap3.get(tableNode);
34552
35400
  const rows = [];
34553
35401
  const rowCount = map.height;
34554
35402
  const colCount$1 = map.width;
@@ -34579,7 +35427,7 @@ function convertTableNodeToArrayOfRows(tableNode) {
34579
35427
  }
34580
35428
  function convertArrayOfRowsToTableNode(tableNode, arrayOfNodes) {
34581
35429
  const newRows = [];
34582
- const map = TableMap2.get(tableNode);
35430
+ const map = TableMap3.get(tableNode);
34583
35431
  const rowCount = map.height;
34584
35432
  const colCount$1 = map.width;
34585
35433
  for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
@@ -34628,7 +35476,7 @@ function findParentNode(predicate, $pos) {
34628
35476
  function getCellsInColumn(columnIndex, selection) {
34629
35477
  const table = findTable(selection.$from);
34630
35478
  if (!table) return;
34631
- const map = TableMap2.get(table.node);
35479
+ const map = TableMap3.get(table.node);
34632
35480
  if (columnIndex < 0 || columnIndex > map.width - 1) return;
34633
35481
  return map.cellsInRect({
34634
35482
  left: columnIndex,
@@ -34649,7 +35497,7 @@ function getCellsInColumn(columnIndex, selection) {
34649
35497
  function getCellsInRow(rowIndex, selection) {
34650
35498
  const table = findTable(selection.$from);
34651
35499
  if (!table) return;
34652
- const map = TableMap2.get(table.node);
35500
+ const map = TableMap3.get(table.node);
34653
35501
  if (rowIndex < 0 || rowIndex > map.height - 1) return;
34654
35502
  return map.cellsInRect({
34655
35503
  left: 0,
@@ -34778,7 +35626,7 @@ function moveColumn(moveColParams) {
34778
35626
  const newTable = moveTableColumn$1(table.node, indexesOriginColumn, indexesTargetColumn, 0);
34779
35627
  tr.replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
34780
35628
  if (!select) return true;
34781
- const map = TableMap2.get(newTable);
35629
+ const map = TableMap3.get(newTable);
34782
35630
  const start = table.start;
34783
35631
  const index = targetIndex;
34784
35632
  const lastCell = map.positionAt(map.height - 1, index, newTable);
@@ -34806,7 +35654,7 @@ function moveRow(moveRowParams) {
34806
35654
  const newTable = moveTableRow$1(table.node, indexesOriginRow, indexesTargetRow, 0);
34807
35655
  tr.replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
34808
35656
  if (!select) return true;
34809
- const map = TableMap2.get(newTable);
35657
+ const map = TableMap3.get(newTable);
34810
35658
  const start = table.start;
34811
35659
  const index = targetIndex;
34812
35660
  const lastCell = map.positionAt(index, map.width - 1, newTable);
@@ -34821,12 +35669,12 @@ function moveTableRow$1(table, indexesOrigin, indexesTarget, direction) {
34821
35669
  rows = moveRowInArrayOfRows(rows, indexesOrigin, indexesTarget, direction);
34822
35670
  return convertArrayOfRowsToTableNode(table, rows);
34823
35671
  }
34824
- function selectedRect2(state) {
35672
+ function selectedRect3(state) {
34825
35673
  const sel = state.selection;
34826
35674
  const $pos = selectionCell(state);
34827
35675
  const table = $pos.node(-1);
34828
35676
  const tableStart = $pos.start(-1);
34829
- const map = TableMap2.get(table);
35677
+ const map = TableMap3.get(table);
34830
35678
  return {
34831
35679
  ...sel instanceof CellSelection ? map.rectBetween(sel.$anchorCell.pos - tableStart, sel.$headCell.pos - tableStart) : map.findCell($pos.pos - tableStart),
34832
35680
  tableStart,
@@ -34839,7 +35687,7 @@ function deprecated_toggleHeader(type) {
34839
35687
  if (!isInTable(state)) return false;
34840
35688
  if (dispatch) {
34841
35689
  const types = tableNodeTypes(state.schema);
34842
- const rect = selectedRect2(state), tr = state.tr;
35690
+ const rect = selectedRect3(state), tr = state.tr;
34843
35691
  const cells = rect.map.cellsInRect(type == "column" ? {
34844
35692
  left: rect.left,
34845
35693
  top: 0,
@@ -34879,7 +35727,7 @@ function toggleHeader(type, options) {
34879
35727
  if (!isInTable(state)) return false;
34880
35728
  if (dispatch) {
34881
35729
  const types = tableNodeTypes(state.schema);
34882
- const rect = selectedRect2(state), tr = state.tr;
35730
+ const rect = selectedRect3(state), tr = state.tr;
34883
35731
  const isHeaderRowEnabled = isHeaderEnabledByType("row", rect, types);
34884
35732
  const isHeaderColumnEnabled = isHeaderEnabledByType("column", rect, types);
34885
35733
  const selectionStartsAt = (type === "column" ? isHeaderRowEnabled : type === "row" ? isHeaderColumnEnabled : false) ? 1 : 0;
@@ -35029,6 +35877,262 @@ var columnResizingPluginKey = new PluginKey4("tableColumnResizing");
35029
35877
  // src/components/UEditor/table-controls.tsx
35030
35878
  var import_lucide_react52 = require("lucide-react");
35031
35879
 
35880
+ // src/components/UEditor/table-layout-model.ts
35881
+ var FALLBACK_TABLE_ROW_HEIGHT = 44;
35882
+ var FALLBACK_TABLE_COLUMN_WIDTH = 160;
35883
+ function getVisibleTableBounds(layout) {
35884
+ const left = Math.max(layout.tableLeft, layout.wrapperLeft);
35885
+ const top = Math.max(layout.tableTop, layout.wrapperTop);
35886
+ const right = Math.min(layout.tableLeft + layout.tableWidth, layout.wrapperLeft + layout.viewportWidth);
35887
+ const bottom = Math.min(layout.tableTop + layout.tableHeight, layout.wrapperTop + layout.viewportHeight);
35888
+ return {
35889
+ left,
35890
+ top,
35891
+ right,
35892
+ bottom,
35893
+ width: Math.max(0, right - left),
35894
+ height: Math.max(0, bottom - top)
35895
+ };
35896
+ }
35897
+ function metricOrFallback(value, fallback) {
35898
+ return Number.isFinite(value) && value > 0 ? value : fallback;
35899
+ }
35900
+ function parsePixelMetric(value) {
35901
+ if (!value) return null;
35902
+ const parsed = Number.parseFloat(value);
35903
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
35904
+ }
35905
+ function getPrimaryCell(table) {
35906
+ const cell = table.querySelector("th,td");
35907
+ return cell instanceof HTMLTableCellElement ? cell : null;
35908
+ }
35909
+ function getLastCell(table) {
35910
+ const lastRow = table.rows.item(table.rows.length - 1);
35911
+ if (!(lastRow instanceof HTMLTableRowElement)) return null;
35912
+ const cell = lastRow.cells.item(lastRow.cells.length - 1);
35913
+ return cell instanceof HTMLTableCellElement ? cell : null;
35914
+ }
35915
+ function getCellFromTarget(target) {
35916
+ const element = resolveEventElement(target);
35917
+ if (!element) return null;
35918
+ const directCell = element.closest("th,td");
35919
+ if (directCell instanceof HTMLTableCellElement) {
35920
+ return directCell;
35921
+ }
35922
+ const table = element.closest("table");
35923
+ if (table instanceof HTMLTableElement) {
35924
+ return getPrimaryCell(table);
35925
+ }
35926
+ return null;
35927
+ }
35928
+ function findTableInfo(editor, pos) {
35929
+ const $pos = editor.state.doc.resolve(pos);
35930
+ for (let depth = $pos.depth; depth > 0; depth -= 1) {
35931
+ const node = $pos.node(depth);
35932
+ if (node.type.name === "table") {
35933
+ return {
35934
+ node,
35935
+ pos: $pos.before(depth),
35936
+ start: $pos.start(depth)
35937
+ };
35938
+ }
35939
+ }
35940
+ return null;
35941
+ }
35942
+ function getCellRelativePosFromDomPos(map, tableStart, domPos) {
35943
+ const relativeDomPos = domPos - tableStart;
35944
+ const seen = /* @__PURE__ */ new Set();
35945
+ for (const relativeCellPos of map.map) {
35946
+ if (seen.has(relativeCellPos)) continue;
35947
+ seen.add(relativeCellPos);
35948
+ if (relativeDomPos === relativeCellPos || relativeDomPos === relativeCellPos + 1) {
35949
+ return relativeCellPos;
35950
+ }
35951
+ }
35952
+ return null;
35953
+ }
35954
+ function buildLogicalColumnMetrics({
35955
+ editor,
35956
+ surface,
35957
+ surfaceRect,
35958
+ tableElement,
35959
+ tableInfo,
35960
+ tableLeft,
35961
+ tableWidth
35962
+ }) {
35963
+ const map = TableMap3.get(tableInfo.node);
35964
+ const fallbackWidth = metricOrFallback(tableWidth / map.width, FALLBACK_TABLE_COLUMN_WIDTH);
35965
+ const firstRow = tableElement.rows.item(0);
35966
+ const visualColumns = [];
35967
+ if (firstRow) {
35968
+ for (const tableCell of Array.from(firstRow.cells)) {
35969
+ if (!(tableCell instanceof HTMLTableCellElement)) continue;
35970
+ const cellPos = editor.view.posAtDOM(tableCell, 0);
35971
+ const relativeCellPos = getCellRelativePosFromDomPos(map, tableInfo.start, cellPos);
35972
+ if (relativeCellPos == null) continue;
35973
+ const cellMapRect = map.findCell(relativeCellPos);
35974
+ const cellRect = tableCell.getBoundingClientRect();
35975
+ const cellStart = cellRect.width > 0 ? cellRect.left - surfaceRect.left + surface.scrollLeft : tableLeft + cellMapRect.left * fallbackWidth;
35976
+ const size = metricOrFallback(cellRect.width, fallbackWidth * Math.max(1, cellMapRect.right - cellMapRect.left));
35977
+ visualColumns.push({
35978
+ index: cellMapRect.left,
35979
+ cellPos: tableInfo.start + relativeCellPos,
35980
+ start: cellStart,
35981
+ size,
35982
+ center: cellStart + size / 2
35983
+ });
35984
+ }
35985
+ }
35986
+ if (visualColumns.length > 0) {
35987
+ return visualColumns.sort((a, b) => a.index - b.index);
35988
+ }
35989
+ const cols = Array.from(tableElement.querySelectorAll("colgroup > col"));
35990
+ const parsedWidths = cols.slice(0, map.width).map((col) => parsePixelMetric(col.style.width) ?? parsePixelMetric(col.getAttribute("width")));
35991
+ const hasCompleteColWidths = parsedWidths.length >= map.width && parsedWidths.every((width) => typeof width === "number");
35992
+ let cursor = tableLeft;
35993
+ return Array.from({ length: map.width }, (_, index) => {
35994
+ const size = hasCompleteColWidths ? parsedWidths[index] : fallbackWidth;
35995
+ const start = hasCompleteColWidths ? cursor : tableLeft + index * fallbackWidth;
35996
+ cursor += size;
35997
+ return {
35998
+ index,
35999
+ cellPos: tableInfo.start + map.positionAt(0, index, tableInfo.node),
36000
+ start,
36001
+ size,
36002
+ center: start + size / 2
36003
+ };
36004
+ });
36005
+ }
36006
+ function buildLogicalRowMetrics({
36007
+ editor,
36008
+ surface,
36009
+ surfaceRect,
36010
+ tableInfo,
36011
+ rows,
36012
+ tableTop,
36013
+ tableHeight,
36014
+ cornerCell
36015
+ }) {
36016
+ const map = TableMap3.get(tableInfo.node);
36017
+ const fallbackHeight = metricOrFallback(tableHeight / map.height, FALLBACK_TABLE_ROW_HEIGHT);
36018
+ const visualRows = [];
36019
+ const seenCellPositions = /* @__PURE__ */ new Set();
36020
+ for (let rowIndex = 0; rowIndex < map.height; rowIndex += 1) {
36021
+ const relativeCellPos = map.map[rowIndex * map.width];
36022
+ if (seenCellPositions.has(relativeCellPos)) continue;
36023
+ seenCellPositions.add(relativeCellPos);
36024
+ const cellMapRect = map.findCell(relativeCellPos);
36025
+ const cellDom = editor.view.nodeDOM(tableInfo.start + relativeCellPos);
36026
+ const tableCell = cellDom instanceof HTMLTableCellElement ? cellDom : null;
36027
+ if (tableCell) {
36028
+ const cellRect = tableCell.getBoundingClientRect();
36029
+ const start = cellRect.height > 0 ? cellRect.top - surfaceRect.top + surface.scrollTop : tableTop + cellMapRect.top * fallbackHeight;
36030
+ const size = metricOrFallback(cellRect.height, fallbackHeight * Math.max(1, cellMapRect.bottom - cellMapRect.top));
36031
+ visualRows.push({
36032
+ index: cellMapRect.top,
36033
+ cellPos: tableInfo.start + relativeCellPos,
36034
+ start,
36035
+ size,
36036
+ center: start + size / 2
36037
+ });
36038
+ }
36039
+ }
36040
+ if (visualRows.length > 0) {
36041
+ return visualRows.sort((a, b) => a.index - b.index);
36042
+ }
36043
+ return rows.map((tableRow, index) => {
36044
+ const rowRect = tableRow.getBoundingClientRect();
36045
+ const anchorCell = tableRow.cells.item(0) ?? cornerCell;
36046
+ const start = rowRect.height > 0 ? rowRect.top - surfaceRect.top + surface.scrollTop : tableTop + index * fallbackHeight;
36047
+ const size = metricOrFallback(rowRect.height, fallbackHeight);
36048
+ return {
36049
+ index,
36050
+ cellPos: editor.view.posAtDOM(anchorCell, 0),
36051
+ start,
36052
+ size,
36053
+ center: start + size / 2
36054
+ };
36055
+ });
36056
+ }
36057
+ function buildTableControlLayout(editor, surface, cell) {
36058
+ const row = cell.closest("tr");
36059
+ const table = cell.closest("table");
36060
+ if (!(row instanceof HTMLTableRowElement) || !(table instanceof HTMLTableElement)) {
36061
+ return null;
36062
+ }
36063
+ const rows = Array.from(table.rows).filter((item) => item instanceof HTMLTableRowElement);
36064
+ const cornerCell = getLastCell(table);
36065
+ const cellPos = editor.view.posAtDOM(cell, 0);
36066
+ const tableInfo = findTableInfo(editor, cellPos);
36067
+ if (rows.length === 0 || !tableInfo || !(cornerCell instanceof HTMLTableCellElement)) {
36068
+ return null;
36069
+ }
36070
+ const map = TableMap3.get(tableInfo.node);
36071
+ const surfaceRect = surface.getBoundingClientRect();
36072
+ const tableRect = table.getBoundingClientRect();
36073
+ const wrapperElement = table.closest(".tableWrapper");
36074
+ const wrapper = wrapperElement instanceof HTMLElement ? wrapperElement : null;
36075
+ const wrapperRect = wrapper?.getBoundingClientRect() ?? tableRect;
36076
+ const tableLeft = tableRect.left - surfaceRect.left + surface.scrollLeft;
36077
+ const tableTop = tableRect.top - surfaceRect.top + surface.scrollTop;
36078
+ const avgRowHeight = metricOrFallback(tableRect.height / rows.length, FALLBACK_TABLE_ROW_HEIGHT);
36079
+ const avgColumnWidth = metricOrFallback(tableRect.width / map.width, FALLBACK_TABLE_COLUMN_WIDTH);
36080
+ const tableWidth = metricOrFallback(tableRect.width, avgColumnWidth * map.width);
36081
+ const tableHeight = metricOrFallback(tableRect.height, avgRowHeight * rows.length);
36082
+ const wrapperLeft = wrapperRect.left - surfaceRect.left + surface.scrollLeft;
36083
+ const wrapperTop = wrapperRect.top - surfaceRect.top + surface.scrollTop;
36084
+ const wrapperWidth = metricOrFallback(wrapperRect.width, tableWidth);
36085
+ const wrapperHeight = metricOrFallback(wrapperRect.height, tableHeight);
36086
+ const viewportWidth = metricOrFallback(wrapper?.clientWidth ?? wrapperRect.width, tableWidth);
36087
+ const viewportHeight = metricOrFallback(wrapper?.clientHeight ?? wrapperRect.height, tableHeight);
36088
+ const verticalScrollbarWidth = Math.max(0, Math.round(wrapperWidth - viewportWidth));
36089
+ const horizontalScrollbarHeight = Math.max(0, Math.round(wrapperHeight - viewportHeight));
36090
+ const rowHandles = buildLogicalRowMetrics({
36091
+ editor,
36092
+ surface,
36093
+ surfaceRect,
36094
+ tableInfo,
36095
+ rows,
36096
+ tableTop,
36097
+ tableHeight,
36098
+ cornerCell
36099
+ });
36100
+ const columnHandles = buildLogicalColumnMetrics({
36101
+ editor,
36102
+ surface,
36103
+ surfaceRect,
36104
+ tableElement: table,
36105
+ tableInfo,
36106
+ tableLeft,
36107
+ tableWidth
36108
+ });
36109
+ const activeCellRelativePos = getCellRelativePosFromDomPos(map, tableInfo.start, cellPos);
36110
+ const activeCellRect = activeCellRelativePos != null ? map.findCell(activeCellRelativePos) : { left: cell.cellIndex, top: row.rowIndex };
36111
+ const normalizedCellPos = activeCellRelativePos != null ? tableInfo.start + activeCellRelativePos : cellPos;
36112
+ return {
36113
+ cellPos: normalizedCellPos,
36114
+ cornerCellPos: tableInfo.start + map.positionAt(map.height - 1, map.width - 1, tableInfo.node),
36115
+ activeRowIndex: activeCellRect.top,
36116
+ activeColumnIndex: activeCellRect.left,
36117
+ tableLeft,
36118
+ tableTop,
36119
+ tableWidth,
36120
+ tableHeight,
36121
+ wrapperLeft,
36122
+ wrapperTop,
36123
+ wrapperWidth,
36124
+ wrapperHeight,
36125
+ viewportWidth,
36126
+ viewportHeight,
36127
+ horizontalScrollbarHeight,
36128
+ verticalScrollbarWidth,
36129
+ avgRowHeight,
36130
+ avgColumnWidth,
36131
+ rowHandles,
36132
+ columnHandles
36133
+ };
36134
+ }
36135
+
35032
36136
  // src/components/UEditor/table-hover-state.ts
35033
36137
  var MENU_HOVER_PADDING = 18;
35034
36138
  var ROW_HANDLE_HOVER_WIDTH = 28;
@@ -35062,18 +36166,18 @@ function buildTableHoverState({
35062
36166
  const directAddRow = targetElement?.closest?.("[data-table-control='add-row']");
35063
36167
  const directRowHandleIndex = directRowHandle instanceof HTMLElement ? Number.parseInt(directRowHandle.dataset.rowHandleIndex ?? "", 10) : Number.NaN;
35064
36168
  const directColumnHandleIndex = directColumnHandle instanceof HTMLElement ? Number.parseInt(directColumnHandle.dataset.columnHandleIndex ?? "", 10) : Number.NaN;
35065
- const visibleTableWidth = Math.min(layout.tableWidth, layout.viewportWidth);
35066
- const visibleTableHeight = Math.min(layout.tableHeight, layout.viewportHeight);
36169
+ const visibleBounds = getVisibleTableBounds(layout);
36170
+ const rowRailTop = layout.wrapperTop + layout.wrapperHeight;
35067
36171
  const isMouseInTable = relativeX >= layout.tableLeft && relativeX <= layout.tableLeft + layout.tableWidth && relativeY >= layout.tableTop && relativeY <= layout.tableTop + layout.tableHeight;
35068
36172
  const rowHandleIndex = Number.isFinite(directRowHandleIndex) ? directRowHandleIndex : layout.rowHandles.find((rowHandle) => relativeX >= layout.tableLeft - ROW_HANDLE_HOVER_WIDTH && relativeX <= layout.tableLeft && Math.abs(relativeY - rowHandle.center) <= HANDLE_HOVER_RADIUS)?.index ?? null;
35069
36173
  const columnHandleIndex = Number.isFinite(directColumnHandleIndex) ? directColumnHandleIndex : layout.columnHandles.find((columnHandle) => relativeY >= layout.tableTop - COLUMN_HANDLE_HOVER_HEIGHT && relativeY <= layout.tableTop && Math.abs(relativeX - columnHandle.center) <= HANDLE_HOVER_RADIUS)?.index ?? null;
35070
36174
  const menuVisible = Boolean(directTableMenu) || isMouseInTable || relativeX >= layout.tableLeft - MENU_HOVER_PADDING && relativeX <= layout.tableLeft + 42 && relativeY >= layout.tableTop - COLUMN_HANDLE_HOVER_HEIGHT && relativeY <= layout.tableTop + MENU_HOVER_PADDING;
35071
36175
  const lastRow = layout.rowHandles[layout.rowHandles.length - 1];
35072
36176
  const lastCol = layout.columnHandles[layout.columnHandles.length - 1];
35073
- const isMouseInLastColumn = lastCol ? relativeX >= lastCol.start && relativeX <= lastCol.start + lastCol.size && relativeY >= layout.tableTop && relativeY <= layout.tableTop + visibleTableHeight : false;
35074
- const addColumnVisible = Boolean(directAddColumn) || relativeX >= layout.tableLeft + visibleTableWidth && relativeX <= layout.tableLeft + visibleTableWidth + ADD_COLUMN_HOVER_WIDTH && relativeY >= layout.tableTop && relativeY <= layout.tableTop + visibleTableHeight || isMouseInLastColumn;
35075
- const isMouseInLastRow = lastRow ? relativeY >= lastRow.start && relativeY <= lastRow.start + lastRow.size && relativeX >= layout.tableLeft && relativeX <= layout.tableLeft + visibleTableWidth : false;
35076
- const addRowVisible = Boolean(directAddRow) || relativeY >= layout.tableTop + visibleTableHeight && relativeY <= layout.tableTop + visibleTableHeight + ADD_ROW_HOVER_HEIGHT && relativeX >= layout.tableLeft && relativeX <= layout.tableLeft + visibleTableWidth || isMouseInLastRow;
36177
+ const isMouseInLastColumn = lastCol ? relativeX >= lastCol.start && relativeX <= lastCol.start + lastCol.size && relativeY >= visibleBounds.top && relativeY <= visibleBounds.bottom : false;
36178
+ const addColumnVisible = Boolean(directAddColumn) || relativeX >= visibleBounds.right && relativeX <= visibleBounds.right + ADD_COLUMN_HOVER_WIDTH && relativeY >= visibleBounds.top && relativeY <= visibleBounds.bottom || isMouseInLastColumn;
36179
+ const isMouseInLastRow = lastRow ? relativeY >= lastRow.start && relativeY <= lastRow.start + lastRow.size && relativeX >= visibleBounds.left && relativeX <= visibleBounds.right : false;
36180
+ const addRowVisible = Boolean(directAddRow) || relativeY >= rowRailTop && relativeY <= rowRailTop + ADD_ROW_HOVER_HEIGHT && relativeX >= visibleBounds.left && relativeX <= visibleBounds.right || isMouseInLastRow;
35077
36181
  return {
35078
36182
  menuVisible,
35079
36183
  addColumnVisible,
@@ -35195,12 +36299,11 @@ function TableAddRails({
35195
36299
  quickAddColumnLabel,
35196
36300
  quickAddRowLabel
35197
36301
  }) {
35198
- const visibleTableWidth = Math.min(layout.tableWidth, layout.viewportWidth);
35199
- const visibleTableHeight = Math.min(layout.tableHeight, layout.viewportHeight);
35200
- const columnRailTop = layout.tableTop;
35201
- const columnRailLeft = layout.tableLeft + visibleTableWidth + ADD_COLUMN_RAIL_GAP;
36302
+ const visibleBounds = getVisibleTableBounds(layout);
36303
+ const columnRailTop = visibleBounds.top;
36304
+ const columnRailLeft = visibleBounds.right + ADD_COLUMN_RAIL_GAP;
35202
36305
  const rowRailTop = layout.wrapperTop + layout.wrapperHeight + ADD_ROW_RAIL_GAP;
35203
- const rowRailLeft = Math.max(layout.tableLeft, layout.wrapperLeft);
36306
+ const rowRailLeft = visibleBounds.left;
35204
36307
  const showColumnRail = controlsVisible || addColumnVisible;
35205
36308
  const showRowRail = controlsVisible || addRowVisible;
35206
36309
  return /* @__PURE__ */ (0, import_jsx_runtime89.jsxs)(import_jsx_runtime89.Fragment, { children: [
@@ -35228,10 +36331,10 @@ function TableAddRails({
35228
36331
  "transition-[opacity,transform,colors] duration-150 hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed"
35229
36332
  ),
35230
36333
  style: {
35231
- top: showColumnRail ? columnRailTop : columnRailTop + Math.max(0, visibleTableHeight / 2 - 24),
36334
+ top: showColumnRail ? columnRailTop : columnRailTop + Math.max(0, visibleBounds.height / 2 - 24),
35232
36335
  left: columnRailLeft,
35233
36336
  width: showColumnRail ? 18 : 12,
35234
- height: showColumnRail ? visibleTableHeight : 48,
36337
+ height: showColumnRail ? visibleBounds.height : 48,
35235
36338
  opacity: showColumnRail ? 1 : 0,
35236
36339
  transform: showColumnRail ? "scale(1)" : "scale(0.92)",
35237
36340
  pointerEvents: showColumnRail ? "auto" : "none"
@@ -35266,8 +36369,8 @@ function TableAddRails({
35266
36369
  ),
35267
36370
  style: {
35268
36371
  top: rowRailTop,
35269
- left: showRowRail ? rowRailLeft : rowRailLeft + Math.max(0, visibleTableWidth / 2 - 24),
35270
- width: showRowRail ? visibleTableWidth : 48,
36372
+ left: showRowRail ? rowRailLeft : rowRailLeft + Math.max(0, visibleBounds.width / 2 - 24),
36373
+ width: showRowRail ? visibleBounds.width : 48,
35271
36374
  height: showRowRail ? 16 : 12,
35272
36375
  opacity: showRowRail ? 1 : 0,
35273
36376
  transform: showRowRail ? "scale(1)" : "scale(0.92)",
@@ -35492,248 +36595,6 @@ function TableColumnHandles({
35492
36595
  }) });
35493
36596
  }
35494
36597
 
35495
- // src/components/UEditor/table-layout-model.ts
35496
- var FALLBACK_TABLE_ROW_HEIGHT = 44;
35497
- var FALLBACK_TABLE_COLUMN_WIDTH = 160;
35498
- function metricOrFallback(value, fallback) {
35499
- return Number.isFinite(value) && value > 0 ? value : fallback;
35500
- }
35501
- function parsePixelMetric(value) {
35502
- if (!value) return null;
35503
- const parsed = Number.parseFloat(value);
35504
- return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
35505
- }
35506
- function getPrimaryCell(table) {
35507
- const cell = table.querySelector("th,td");
35508
- return cell instanceof HTMLTableCellElement ? cell : null;
35509
- }
35510
- function getLastCell(table) {
35511
- const lastRow = table.rows.item(table.rows.length - 1);
35512
- if (!(lastRow instanceof HTMLTableRowElement)) return null;
35513
- const cell = lastRow.cells.item(lastRow.cells.length - 1);
35514
- return cell instanceof HTMLTableCellElement ? cell : null;
35515
- }
35516
- function getCellFromTarget(target) {
35517
- const element = resolveEventElement(target);
35518
- if (!element) return null;
35519
- const directCell = element.closest("th,td");
35520
- if (directCell instanceof HTMLTableCellElement) {
35521
- return directCell;
35522
- }
35523
- const table = element.closest("table");
35524
- if (table instanceof HTMLTableElement) {
35525
- return getPrimaryCell(table);
35526
- }
35527
- return null;
35528
- }
35529
- function findTableInfo(editor, pos) {
35530
- const $pos = editor.state.doc.resolve(pos);
35531
- for (let depth = $pos.depth; depth > 0; depth -= 1) {
35532
- const node = $pos.node(depth);
35533
- if (node.type.name === "table") {
35534
- return {
35535
- node,
35536
- pos: $pos.before(depth),
35537
- start: $pos.start(depth)
35538
- };
35539
- }
35540
- }
35541
- return null;
35542
- }
35543
- function getCellRelativePosFromDomPos(map, tableStart, domPos) {
35544
- const relativeDomPos = domPos - tableStart;
35545
- const seen = /* @__PURE__ */ new Set();
35546
- for (const relativeCellPos of map.map) {
35547
- if (seen.has(relativeCellPos)) continue;
35548
- seen.add(relativeCellPos);
35549
- if (relativeDomPos === relativeCellPos || relativeDomPos === relativeCellPos + 1) {
35550
- return relativeCellPos;
35551
- }
35552
- }
35553
- return null;
35554
- }
35555
- function buildLogicalColumnMetrics({
35556
- editor,
35557
- surface,
35558
- surfaceRect,
35559
- tableElement,
35560
- tableInfo,
35561
- tableLeft,
35562
- tableWidth
35563
- }) {
35564
- const map = TableMap2.get(tableInfo.node);
35565
- const fallbackWidth = metricOrFallback(tableWidth / map.width, FALLBACK_TABLE_COLUMN_WIDTH);
35566
- const firstRow = tableElement.rows.item(0);
35567
- const visualColumns = [];
35568
- if (firstRow) {
35569
- for (const tableCell of Array.from(firstRow.cells)) {
35570
- if (!(tableCell instanceof HTMLTableCellElement)) continue;
35571
- const cellPos = editor.view.posAtDOM(tableCell, 0);
35572
- const relativeCellPos = getCellRelativePosFromDomPos(map, tableInfo.start, cellPos);
35573
- if (relativeCellPos == null) continue;
35574
- const cellMapRect = map.findCell(relativeCellPos);
35575
- const cellRect = tableCell.getBoundingClientRect();
35576
- const cellStart = cellRect.width > 0 ? cellRect.left - surfaceRect.left + surface.scrollLeft : tableLeft + cellMapRect.left * fallbackWidth;
35577
- const size = metricOrFallback(cellRect.width, fallbackWidth * Math.max(1, cellMapRect.right - cellMapRect.left));
35578
- visualColumns.push({
35579
- index: cellMapRect.left,
35580
- cellPos: tableInfo.start + relativeCellPos,
35581
- start: cellStart,
35582
- size,
35583
- center: cellStart + size / 2
35584
- });
35585
- }
35586
- }
35587
- if (visualColumns.length > 0) {
35588
- return visualColumns.sort((a, b) => a.index - b.index);
35589
- }
35590
- const cols = Array.from(tableElement.querySelectorAll("colgroup > col"));
35591
- const parsedWidths = cols.slice(0, map.width).map((col) => parsePixelMetric(col.style.width) ?? parsePixelMetric(col.getAttribute("width")));
35592
- const hasCompleteColWidths = parsedWidths.length >= map.width && parsedWidths.every((width) => typeof width === "number");
35593
- let cursor = tableLeft;
35594
- return Array.from({ length: map.width }, (_, index) => {
35595
- const size = hasCompleteColWidths ? parsedWidths[index] : fallbackWidth;
35596
- const start = hasCompleteColWidths ? cursor : tableLeft + index * fallbackWidth;
35597
- cursor += size;
35598
- return {
35599
- index,
35600
- cellPos: tableInfo.start + map.positionAt(0, index, tableInfo.node),
35601
- start,
35602
- size,
35603
- center: start + size / 2
35604
- };
35605
- });
35606
- }
35607
- function buildLogicalRowMetrics({
35608
- editor,
35609
- surface,
35610
- surfaceRect,
35611
- tableInfo,
35612
- rows,
35613
- tableTop,
35614
- tableHeight,
35615
- cornerCell
35616
- }) {
35617
- const map = TableMap2.get(tableInfo.node);
35618
- const fallbackHeight = metricOrFallback(tableHeight / map.height, FALLBACK_TABLE_ROW_HEIGHT);
35619
- const visualRows = [];
35620
- const seenCellPositions = /* @__PURE__ */ new Set();
35621
- for (let rowIndex = 0; rowIndex < map.height; rowIndex += 1) {
35622
- const relativeCellPos = map.map[rowIndex * map.width];
35623
- if (seenCellPositions.has(relativeCellPos)) continue;
35624
- seenCellPositions.add(relativeCellPos);
35625
- const cellMapRect = map.findCell(relativeCellPos);
35626
- const cellDom = editor.view.nodeDOM(tableInfo.start + relativeCellPos);
35627
- const tableCell = cellDom instanceof HTMLTableCellElement ? cellDom : null;
35628
- if (tableCell) {
35629
- const cellRect = tableCell.getBoundingClientRect();
35630
- const start = cellRect.height > 0 ? cellRect.top - surfaceRect.top + surface.scrollTop : tableTop + cellMapRect.top * fallbackHeight;
35631
- const size = metricOrFallback(cellRect.height, fallbackHeight * Math.max(1, cellMapRect.bottom - cellMapRect.top));
35632
- visualRows.push({
35633
- index: cellMapRect.top,
35634
- cellPos: tableInfo.start + relativeCellPos,
35635
- start,
35636
- size,
35637
- center: start + size / 2
35638
- });
35639
- }
35640
- }
35641
- if (visualRows.length > 0) {
35642
- return visualRows.sort((a, b) => a.index - b.index);
35643
- }
35644
- return rows.map((tableRow, index) => {
35645
- const rowRect = tableRow.getBoundingClientRect();
35646
- const anchorCell = tableRow.cells.item(0) ?? cornerCell;
35647
- const start = rowRect.height > 0 ? rowRect.top - surfaceRect.top + surface.scrollTop : tableTop + index * fallbackHeight;
35648
- const size = metricOrFallback(rowRect.height, fallbackHeight);
35649
- return {
35650
- index,
35651
- cellPos: editor.view.posAtDOM(anchorCell, 0),
35652
- start,
35653
- size,
35654
- center: start + size / 2
35655
- };
35656
- });
35657
- }
35658
- function buildTableControlLayout(editor, surface, cell) {
35659
- const row = cell.closest("tr");
35660
- const table = cell.closest("table");
35661
- if (!(row instanceof HTMLTableRowElement) || !(table instanceof HTMLTableElement)) {
35662
- return null;
35663
- }
35664
- const rows = Array.from(table.rows).filter((item) => item instanceof HTMLTableRowElement);
35665
- const cornerCell = getLastCell(table);
35666
- const cellPos = editor.view.posAtDOM(cell, 0);
35667
- const tableInfo = findTableInfo(editor, cellPos);
35668
- if (rows.length === 0 || !tableInfo || !(cornerCell instanceof HTMLTableCellElement)) {
35669
- return null;
35670
- }
35671
- const map = TableMap2.get(tableInfo.node);
35672
- const surfaceRect = surface.getBoundingClientRect();
35673
- const tableRect = table.getBoundingClientRect();
35674
- const wrapperElement = table.closest(".tableWrapper");
35675
- const wrapper = wrapperElement instanceof HTMLElement ? wrapperElement : null;
35676
- const wrapperRect = wrapper?.getBoundingClientRect() ?? tableRect;
35677
- const tableLeft = tableRect.left - surfaceRect.left + surface.scrollLeft;
35678
- const tableTop = tableRect.top - surfaceRect.top + surface.scrollTop;
35679
- const avgRowHeight = metricOrFallback(tableRect.height / rows.length, FALLBACK_TABLE_ROW_HEIGHT);
35680
- const avgColumnWidth = metricOrFallback(tableRect.width / map.width, FALLBACK_TABLE_COLUMN_WIDTH);
35681
- const tableWidth = metricOrFallback(tableRect.width, avgColumnWidth * map.width);
35682
- const tableHeight = metricOrFallback(tableRect.height, avgRowHeight * rows.length);
35683
- const wrapperLeft = wrapperRect.left - surfaceRect.left + surface.scrollLeft;
35684
- const wrapperTop = wrapperRect.top - surfaceRect.top + surface.scrollTop;
35685
- const wrapperWidth = metricOrFallback(wrapperRect.width, tableWidth);
35686
- const wrapperHeight = metricOrFallback(wrapperRect.height, tableHeight);
35687
- const viewportWidth = metricOrFallback(wrapper?.clientWidth ?? wrapperRect.width, tableWidth);
35688
- const viewportHeight = metricOrFallback(wrapper?.clientHeight ?? wrapperRect.height, tableHeight);
35689
- const verticalScrollbarWidth = Math.max(0, Math.round(wrapperWidth - viewportWidth));
35690
- const horizontalScrollbarHeight = Math.max(0, Math.round(wrapperHeight - viewportHeight));
35691
- const rowHandles = buildLogicalRowMetrics({
35692
- editor,
35693
- surface,
35694
- surfaceRect,
35695
- tableInfo,
35696
- rows,
35697
- tableTop,
35698
- tableHeight,
35699
- cornerCell
35700
- });
35701
- const columnHandles = buildLogicalColumnMetrics({
35702
- editor,
35703
- surface,
35704
- surfaceRect,
35705
- tableElement: table,
35706
- tableInfo,
35707
- tableLeft,
35708
- tableWidth
35709
- });
35710
- const activeCellRelativePos = getCellRelativePosFromDomPos(map, tableInfo.start, cellPos);
35711
- const activeCellRect = activeCellRelativePos != null ? map.findCell(activeCellRelativePos) : { left: cell.cellIndex, top: row.rowIndex };
35712
- const normalizedCellPos = activeCellRelativePos != null ? tableInfo.start + activeCellRelativePos : cellPos;
35713
- return {
35714
- cellPos: normalizedCellPos,
35715
- cornerCellPos: tableInfo.start + map.positionAt(map.height - 1, map.width - 1, tableInfo.node),
35716
- activeRowIndex: activeCellRect.top,
35717
- activeColumnIndex: activeCellRect.left,
35718
- tableLeft,
35719
- tableTop,
35720
- tableWidth,
35721
- tableHeight,
35722
- wrapperLeft,
35723
- wrapperTop,
35724
- wrapperWidth,
35725
- wrapperHeight,
35726
- viewportWidth,
35727
- viewportHeight,
35728
- horizontalScrollbarHeight,
35729
- verticalScrollbarWidth,
35730
- avgRowHeight,
35731
- avgColumnWidth,
35732
- rowHandles,
35733
- columnHandles
35734
- };
35735
- }
35736
-
35737
36598
  // src/components/UEditor/table-controls.tsx
35738
36599
  var import_jsx_runtime92 = require("react/jsx-runtime");
35739
36600
  var TABLE_MENU_TOP_OFFSET = 10;
@@ -37680,6 +38541,7 @@ var UEditor = import_react69.default.forwardRef(({
37680
38541
  const effectivePlaceholder = placeholder ?? t("placeholder");
37681
38542
  const inFlightPrepareRef = (0, import_react69.useRef)(null);
37682
38543
  const lastAppliedContentRef = (0, import_react69.useRef)(content ?? "");
38544
+ const scheduledFormulaRecalculateRef = (0, import_react69.useRef)(false);
37683
38545
  const resolvedUploadFile = (0, import_react69.useMemo)(() => {
37684
38546
  if (uploadFile) return uploadFile;
37685
38547
  if (uploadFileForSave) {
@@ -37743,7 +38605,16 @@ var UEditor = import_react69.default.forwardRef(({
37743
38605
  class: UEDITOR_PROSEMIRROR_CLASS_NAME
37744
38606
  }
37745
38607
  },
37746
- onUpdate: ({ editor: editor2 }) => {
38608
+ onUpdate: ({ editor: editor2, transaction }) => {
38609
+ if (!transaction.getMeta(UEDITOR_TABLE_FORMULA_RECALCULATE_META) && !scheduledFormulaRecalculateRef.current) {
38610
+ scheduledFormulaRecalculateRef.current = true;
38611
+ queueMicrotask(() => {
38612
+ scheduledFormulaRecalculateRef.current = false;
38613
+ if (!editor2.isDestroyed) {
38614
+ recalculateAllTableFormulas(editor2);
38615
+ }
38616
+ });
38617
+ }
37747
38618
  const html = editor2.getHTML();
37748
38619
  onChange?.(html);
37749
38620
  onHtmlChange?.(html);
@@ -37781,6 +38652,14 @@ var UEditor = import_react69.default.forwardRef(({
37781
38652
  }),
37782
38653
  [content, editor, uploadImageForSave, uploadFileForSave, uploadImageConcurrency]
37783
38654
  );
38655
+ (0, import_react69.useEffect)(() => {
38656
+ if (!editor) return;
38657
+ queueMicrotask(() => {
38658
+ if (!editor.isDestroyed) {
38659
+ recalculateAllTableFormulas(editor);
38660
+ }
38661
+ });
38662
+ }, [editor]);
37784
38663
  (0, import_react69.useEffect)(() => {
37785
38664
  if (!editor) return;
37786
38665
  const nextContent = content ?? "";
@@ -37790,6 +38669,11 @@ var UEditor = import_react69.default.forwardRef(({
37790
38669
  Promise.resolve().then(() => {
37791
38670
  if (!editor.isDestroyed) {
37792
38671
  editor.commands.setContent(nextContent, { emitUpdate: false });
38672
+ queueMicrotask(() => {
38673
+ if (!editor.isDestroyed) {
38674
+ recalculateAllTableFormulas(editor);
38675
+ }
38676
+ });
37793
38677
  }
37794
38678
  });
37795
38679
  }