@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.js CHANGED
@@ -843,6 +843,10 @@ var en_default = {
843
843
  borderWidth: "Border Width",
844
844
  borderColor: "Border Color",
845
845
  clearBorder: "Clear Border",
846
+ formula: "Formula",
847
+ apply: "Apply",
848
+ clear: "Clear",
849
+ recalculate: "Recalculate",
846
850
  done: "Done"
847
851
  },
848
852
  callout: {
@@ -1219,6 +1223,10 @@ var vi_default = {
1219
1223
  borderWidth: "\u0110\u1ED9 d\xE0y vi\u1EC1n",
1220
1224
  borderColor: "M\xE0u vi\u1EC1n",
1221
1225
  clearBorder: "X\xF3a vi\u1EC1n",
1226
+ formula: "C\xF4ng th\u1EE9c",
1227
+ apply: "\xC1p d\u1EE5ng",
1228
+ clear: "X\xF3a",
1229
+ recalculate: "T\xEDnh l\u1EA1i",
1222
1230
  done: "Xong"
1223
1231
  },
1224
1232
  callout: {
@@ -25985,6 +25993,10 @@ function getTableCellAttrs(cell, styles, defaultBackgroundColor) {
25985
25993
  if (borderAttrs.borderColor) attrs.borderColor = borderAttrs.borderColor;
25986
25994
  if (borderAttrs.borderStyle) attrs.borderStyle = borderAttrs.borderStyle;
25987
25995
  if (borderAttrs.borderWidth) attrs.borderWidth = borderAttrs.borderWidth;
25996
+ if (cell.getAttribute("data-cell-id")) attrs.cellId = cell.getAttribute("data-cell-id") ?? void 0;
25997
+ if (cell.getAttribute("data-number-format")) attrs.numberFormat = cell.getAttribute("data-number-format") ?? void 0;
25998
+ if (cell.getAttribute("data-formula")) attrs.formula = cell.getAttribute("data-formula") ?? void 0;
25999
+ if (cell.getAttribute("data-computed-value")) attrs.computedValue = cell.getAttribute("data-computed-value") ?? void 0;
25988
26000
  if (colspan > 1) attrs.colspan = colspan;
25989
26001
  if (rowspan > 1) attrs.rowspan = rowspan;
25990
26002
  if (colwidth) attrs.colwidth = colwidth;
@@ -27612,6 +27624,16 @@ var CodeBlockView = ({
27612
27624
  };
27613
27625
 
27614
27626
  // src/components/UEditor/extensions.ts
27627
+ function getFormulaStateAttributes(attributes) {
27628
+ const formula = attributes["data-formula"];
27629
+ if (!formula) {
27630
+ return {};
27631
+ }
27632
+ const computedValue = String(attributes["data-computed-value"] ?? "");
27633
+ return {
27634
+ "data-formula-state": computedValue.startsWith("#") ? "error" : "computed"
27635
+ };
27636
+ }
27615
27637
  var CustomTableCell = TableCell2.extend({
27616
27638
  addAttributes() {
27617
27639
  return {
@@ -27657,6 +27679,46 @@ var CustomTableCell = TableCell2.extend({
27657
27679
  "data-border-width": attributes.borderWidth
27658
27680
  };
27659
27681
  }
27682
+ },
27683
+ cellId: {
27684
+ default: null,
27685
+ parseHTML: (element) => element.getAttribute("data-cell-id") || null,
27686
+ renderHTML: (attributes) => {
27687
+ if (!attributes.cellId) return {};
27688
+ return {
27689
+ "data-cell-id": attributes.cellId
27690
+ };
27691
+ }
27692
+ },
27693
+ numberFormat: {
27694
+ default: null,
27695
+ parseHTML: (element) => element.getAttribute("data-number-format") || null,
27696
+ renderHTML: (attributes) => {
27697
+ if (!attributes.numberFormat) return {};
27698
+ return {
27699
+ "data-number-format": attributes.numberFormat
27700
+ };
27701
+ }
27702
+ },
27703
+ formula: {
27704
+ default: null,
27705
+ parseHTML: (element) => element.getAttribute("data-formula") || null,
27706
+ renderHTML: (attributes) => {
27707
+ if (!attributes.formula) return {};
27708
+ return {
27709
+ "data-formula": attributes.formula
27710
+ };
27711
+ }
27712
+ },
27713
+ computedValue: {
27714
+ default: null,
27715
+ parseHTML: (element) => element.getAttribute("data-computed-value") || null,
27716
+ renderHTML: (attributes) => {
27717
+ if (!attributes.computedValue) return {};
27718
+ return {
27719
+ "data-computed-value": attributes.computedValue
27720
+ };
27721
+ }
27660
27722
  }
27661
27723
  };
27662
27724
  },
@@ -27674,7 +27736,7 @@ var CustomTableCell = TableCell2.extend({
27674
27736
  mergedStyle = mergedStyle.replace(/^;/, "").trim();
27675
27737
  return [
27676
27738
  "td",
27677
- mergeAttributes5(this.options.HTMLAttributes, HTMLAttributes, mergedStyle ? { style: mergedStyle } : {}),
27739
+ mergeAttributes5(this.options.HTMLAttributes, HTMLAttributes, getFormulaStateAttributes(HTMLAttributes), mergedStyle ? { style: mergedStyle } : {}),
27678
27740
  0
27679
27741
  ];
27680
27742
  }
@@ -27724,6 +27786,46 @@ var CustomTableHeader = TableHeader2.extend({
27724
27786
  "data-border-width": attributes.borderWidth
27725
27787
  };
27726
27788
  }
27789
+ },
27790
+ cellId: {
27791
+ default: null,
27792
+ parseHTML: (element) => element.getAttribute("data-cell-id") || null,
27793
+ renderHTML: (attributes) => {
27794
+ if (!attributes.cellId) return {};
27795
+ return {
27796
+ "data-cell-id": attributes.cellId
27797
+ };
27798
+ }
27799
+ },
27800
+ numberFormat: {
27801
+ default: null,
27802
+ parseHTML: (element) => element.getAttribute("data-number-format") || null,
27803
+ renderHTML: (attributes) => {
27804
+ if (!attributes.numberFormat) return {};
27805
+ return {
27806
+ "data-number-format": attributes.numberFormat
27807
+ };
27808
+ }
27809
+ },
27810
+ formula: {
27811
+ default: null,
27812
+ parseHTML: (element) => element.getAttribute("data-formula") || null,
27813
+ renderHTML: (attributes) => {
27814
+ if (!attributes.formula) return {};
27815
+ return {
27816
+ "data-formula": attributes.formula
27817
+ };
27818
+ }
27819
+ },
27820
+ computedValue: {
27821
+ default: null,
27822
+ parseHTML: (element) => element.getAttribute("data-computed-value") || null,
27823
+ renderHTML: (attributes) => {
27824
+ if (!attributes.computedValue) return {};
27825
+ return {
27826
+ "data-computed-value": attributes.computedValue
27827
+ };
27828
+ }
27727
27829
  }
27728
27830
  };
27729
27831
  },
@@ -27741,7 +27843,7 @@ var CustomTableHeader = TableHeader2.extend({
27741
27843
  mergedStyle = mergedStyle.replace(/^;/, "").trim();
27742
27844
  return [
27743
27845
  "th",
27744
- mergeAttributes5(this.options.HTMLAttributes, HTMLAttributes, mergedStyle ? { style: mergedStyle } : {}),
27846
+ mergeAttributes5(this.options.HTMLAttributes, HTMLAttributes, getFormulaStateAttributes(HTMLAttributes), mergedStyle ? { style: mergedStyle } : {}),
27745
27847
  0
27746
27848
  ];
27747
27849
  }
@@ -28474,6 +28576,34 @@ function collectChildren(node) {
28474
28576
  function createEmptyCellNode(cellNode) {
28475
28577
  return cellNode.type.createAndFill(cellNode.attrs) ?? cellNode;
28476
28578
  }
28579
+ function createCellCopyForColumnDuplicate(cellNode) {
28580
+ return cellNode.type.create(cellNode.attrs, cellNode.content);
28581
+ }
28582
+ function getTableRows(tableNode) {
28583
+ const rows = [];
28584
+ tableNode.forEach((rowNode, rowOffset) => {
28585
+ const cells = [];
28586
+ rowNode.forEach((cellNode, cellOffset, index) => {
28587
+ cells.push({
28588
+ index,
28589
+ node: cellNode,
28590
+ relativePos: rowOffset + 1 + cellOffset
28591
+ });
28592
+ });
28593
+ rows.push({
28594
+ node: rowNode,
28595
+ cells
28596
+ });
28597
+ });
28598
+ return rows;
28599
+ }
28600
+ function safeFindCell(map, relativePos) {
28601
+ try {
28602
+ return map.findCell(relativePos);
28603
+ } catch {
28604
+ return null;
28605
+ }
28606
+ }
28477
28607
  function getSelectedTableRect(editor) {
28478
28608
  const cellSelection = getCellSelectionPositions(editor.state.selection);
28479
28609
  if (cellSelection) {
@@ -28599,34 +28729,49 @@ function duplicateTableRowAt(editor, rowIndex, cellPos) {
28599
28729
  }
28600
28730
  function clearTableRowAt(editor, rowIndex, cellPos) {
28601
28731
  return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
28602
- const rows = collectChildren(tableNode);
28603
- const rowNode = rows[rowIndex];
28604
- if (!rowNode) return null;
28605
- const cells = collectChildren(rowNode).map((cellNode) => createEmptyCellNode(cellNode));
28606
- rows[rowIndex] = rowNode.type.create(rowNode.attrs, cells);
28732
+ const map = TableMap.get(tableNode);
28733
+ if (rowIndex < 0 || rowIndex >= map.height) return null;
28734
+ const rows = getTableRows(tableNode).map((rowInfo) => {
28735
+ const cells = collectChildren(rowInfo.node);
28736
+ for (const entry of rowInfo.cells) {
28737
+ const rect = safeFindCell(map, entry.relativePos);
28738
+ if (!rect || rect.top > rowIndex || rowIndex >= rect.bottom) continue;
28739
+ cells[entry.index] = createEmptyCellNode(entry.node);
28740
+ }
28741
+ return rowInfo.node.type.create(rowInfo.node.attrs, cells);
28742
+ });
28607
28743
  return tableNode.type.create(tableNode.attrs, rows);
28608
28744
  });
28609
28745
  }
28610
28746
  function duplicateTableColumnAt(editor, columnIndex, cellPos) {
28611
28747
  return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
28612
- const rows = collectChildren(tableNode).map((rowNode) => {
28613
- const cells = collectChildren(rowNode);
28614
- const cellNode = cells[columnIndex];
28615
- if (!cellNode) return rowNode;
28616
- cells.splice(columnIndex + 1, 0, cellNode.copy(cellNode.content));
28617
- return rowNode.type.create(rowNode.attrs, cells);
28748
+ const map = TableMap.get(tableNode);
28749
+ if (columnIndex < 0 || columnIndex >= map.width) return null;
28750
+ const rows = getTableRows(tableNode).map((rowInfo, rowIndex) => {
28751
+ const cells = collectChildren(rowInfo.node);
28752
+ const sourceCell = rowInfo.cells.find((entry) => {
28753
+ const rect = safeFindCell(map, entry.relativePos);
28754
+ return rect && rect.top === rowIndex && rect.left <= columnIndex && columnIndex < rect.right;
28755
+ });
28756
+ if (!sourceCell) return rowInfo.node;
28757
+ cells.splice(sourceCell.index + 1, 0, createCellCopyForColumnDuplicate(sourceCell.node));
28758
+ return rowInfo.node.type.create(rowInfo.node.attrs, cells);
28618
28759
  });
28619
28760
  return tableNode.type.create(tableNode.attrs, rows);
28620
28761
  });
28621
28762
  }
28622
28763
  function clearTableColumnAt(editor, columnIndex, cellPos) {
28623
28764
  return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
28624
- const rows = collectChildren(tableNode).map((rowNode) => {
28625
- const cells = collectChildren(rowNode);
28626
- const cellNode = cells[columnIndex];
28627
- if (!cellNode) return rowNode;
28628
- cells[columnIndex] = createEmptyCellNode(cellNode);
28629
- return rowNode.type.create(rowNode.attrs, cells);
28765
+ const map = TableMap.get(tableNode);
28766
+ if (columnIndex < 0 || columnIndex >= map.width) return null;
28767
+ const rows = getTableRows(tableNode).map((rowInfo) => {
28768
+ const cells = collectChildren(rowInfo.node);
28769
+ for (const entry of rowInfo.cells) {
28770
+ const rect = safeFindCell(map, entry.relativePos);
28771
+ if (!rect || rect.left > columnIndex || columnIndex >= rect.right) continue;
28772
+ cells[entry.index] = createEmptyCellNode(entry.node);
28773
+ }
28774
+ return rowInfo.node.type.create(rowInfo.node.attrs, cells);
28630
28775
  });
28631
28776
  return tableNode.type.create(tableNode.attrs, rows);
28632
28777
  });
@@ -29678,7 +29823,7 @@ var EditorToolbar = ({
29678
29823
  // src/components/UEditor/menus.tsx
29679
29824
  import { useCallback as useCallback22, useEffect as useEffect36, useMemo as useMemo23, useRef as useRef33, useState as useState48 } from "react";
29680
29825
  import { useEditorState as useEditorState2 } from "@tiptap/react";
29681
- import { isInTable as isSelectionInTable, setCellAttr } from "@tiptap/pm/tables";
29826
+ import { isInTable as isSelectionInTable, setCellAttr as setCellAttr2 } from "@tiptap/pm/tables";
29682
29827
  import { createPortal as createPortal8 } from "react-dom";
29683
29828
  import {
29684
29829
  AlignCenter as AlignCenter2,
@@ -29700,13 +29845,620 @@ import {
29700
29845
  Strikethrough as StrikethroughIcon2,
29701
29846
  Edit2,
29702
29847
  Unlink,
29703
- ExternalLink as ExternalLink3
29848
+ ExternalLink as ExternalLink3,
29849
+ Sigma
29704
29850
  } from "lucide-react";
29851
+
29852
+ // src/components/UEditor/table-formula-commands.ts
29853
+ import { selectedRect as selectedRect2, setCellAttr, TableMap as TableMap2 } from "@tiptap/pm/tables";
29854
+
29855
+ // src/components/UEditor/table-formula.ts
29856
+ var CELL_ADDRESS_RE = /^([A-Z]+)([1-9]\d*)$/i;
29857
+ var CELL_RANGE_RE = /^([A-Z]+[1-9]\d*):([A-Z]+[1-9]\d*)$/i;
29858
+ var SUPPORTED_FUNCTIONS = /* @__PURE__ */ new Set(["SUM", "AVG", "MIN", "MAX", "COUNT"]);
29859
+ function columnNameToIndex(columnName) {
29860
+ const normalized = columnName.trim().toUpperCase();
29861
+ if (!/^[A-Z]+$/.test(normalized)) {
29862
+ return -1;
29863
+ }
29864
+ let index = 0;
29865
+ for (const char of normalized) {
29866
+ index = index * 26 + char.charCodeAt(0) - 64;
29867
+ }
29868
+ return index - 1;
29869
+ }
29870
+ function indexToColumnName(index) {
29871
+ if (!Number.isInteger(index) || index < 0) {
29872
+ return "";
29873
+ }
29874
+ let value = index + 1;
29875
+ let name = "";
29876
+ while (value > 0) {
29877
+ const remainder = (value - 1) % 26;
29878
+ name = String.fromCharCode(65 + remainder) + name;
29879
+ value = Math.floor((value - 1) / 26);
29880
+ }
29881
+ return name;
29882
+ }
29883
+ function parseTableCellAddress(input) {
29884
+ const match = input.trim().match(CELL_ADDRESS_RE);
29885
+ if (!match) {
29886
+ return null;
29887
+ }
29888
+ const column = columnNameToIndex(match[1] ?? "");
29889
+ const row = Number.parseInt(match[2] ?? "", 10) - 1;
29890
+ if (column < 0 || row < 0) {
29891
+ return null;
29892
+ }
29893
+ return {
29894
+ column,
29895
+ row,
29896
+ label: `${indexToColumnName(column)}${row + 1}`
29897
+ };
29898
+ }
29899
+ function parseTableCellRange(input) {
29900
+ const match = input.trim().match(CELL_RANGE_RE);
29901
+ if (!match) {
29902
+ return null;
29903
+ }
29904
+ const from = parseTableCellAddress(match[1] ?? "");
29905
+ const to = parseTableCellAddress(match[2] ?? "");
29906
+ if (!from || !to) {
29907
+ return null;
29908
+ }
29909
+ return { from, to };
29910
+ }
29911
+ function getTableCellRangeLabels(range) {
29912
+ const startColumn = Math.min(range.from.column, range.to.column);
29913
+ const endColumn = Math.max(range.from.column, range.to.column);
29914
+ const startRow = Math.min(range.from.row, range.to.row);
29915
+ const endRow = Math.max(range.from.row, range.to.row);
29916
+ const labels = [];
29917
+ for (let row = startRow; row <= endRow; row += 1) {
29918
+ for (let column = startColumn; column <= endColumn; column += 1) {
29919
+ labels.push(`${indexToColumnName(column)}${row + 1}`);
29920
+ }
29921
+ }
29922
+ return labels;
29923
+ }
29924
+ function normalizeTableFormula(formula) {
29925
+ return formula.trim().replace(/^=/, "").trim();
29926
+ }
29927
+ function formatFormulaError(error) {
29928
+ return `#${error.toUpperCase()}`;
29929
+ }
29930
+ function getTableFormulaReferences(formula) {
29931
+ const normalized = normalizeTableFormula(formula);
29932
+ if (!normalized) return [];
29933
+ const tokens = tokenizeFormula(normalized);
29934
+ if (!tokens) return [];
29935
+ const references = /* @__PURE__ */ new Set();
29936
+ for (const token of tokens) {
29937
+ if (token.type === "cell") {
29938
+ references.add(token.value);
29939
+ } else if (token.type === "range") {
29940
+ const range = parseTableCellRange(token.value);
29941
+ if (!range) continue;
29942
+ for (const label of getTableCellRangeLabels(range)) {
29943
+ references.add(label);
29944
+ }
29945
+ }
29946
+ }
29947
+ return Array.from(references);
29948
+ }
29949
+ function buildTableFormulaDependencyGraph(cells) {
29950
+ const dependencies = /* @__PURE__ */ new Map();
29951
+ const dependents = /* @__PURE__ */ new Map();
29952
+ const formulas = /* @__PURE__ */ new Map();
29953
+ for (const cell of cells) {
29954
+ const label = cell.label.toUpperCase();
29955
+ formulas.set(label, cell.formula);
29956
+ dependencies.set(label, new Set(getTableFormulaReferences(cell.formula)));
29957
+ if (!dependents.has(label)) {
29958
+ dependents.set(label, /* @__PURE__ */ new Set());
29959
+ }
29960
+ }
29961
+ for (const [label, refs] of dependencies) {
29962
+ for (const ref of refs) {
29963
+ if (!dependents.has(ref)) {
29964
+ dependents.set(ref, /* @__PURE__ */ new Set());
29965
+ }
29966
+ dependents.get(ref)?.add(label);
29967
+ }
29968
+ }
29969
+ return { dependencies, dependents, formulas };
29970
+ }
29971
+ function getTableFormulaCircularReferences(graph) {
29972
+ const visiting = /* @__PURE__ */ new Set();
29973
+ const visited = /* @__PURE__ */ new Set();
29974
+ const circular = /* @__PURE__ */ new Set();
29975
+ const stack = [];
29976
+ const visit = (label) => {
29977
+ if (visiting.has(label)) {
29978
+ const start = stack.indexOf(label);
29979
+ for (const cycleLabel of start >= 0 ? stack.slice(start) : [label]) {
29980
+ circular.add(cycleLabel);
29981
+ }
29982
+ return;
29983
+ }
29984
+ if (visited.has(label)) return;
29985
+ visiting.add(label);
29986
+ stack.push(label);
29987
+ for (const ref of graph.dependencies.get(label) ?? []) {
29988
+ if (graph.formulas.has(ref)) {
29989
+ visit(ref);
29990
+ }
29991
+ }
29992
+ stack.pop();
29993
+ visiting.delete(label);
29994
+ visited.add(label);
29995
+ };
29996
+ for (const label of graph.formulas.keys()) {
29997
+ visit(label);
29998
+ }
29999
+ return circular;
30000
+ }
30001
+ function getTableFormulaRecalculationOrder(graph) {
30002
+ const circular = getTableFormulaCircularReferences(graph);
30003
+ const visited = /* @__PURE__ */ new Set();
30004
+ const order = [];
30005
+ const visit = (label) => {
30006
+ if (visited.has(label) || circular.has(label)) return;
30007
+ visited.add(label);
30008
+ for (const ref of graph.dependencies.get(label) ?? []) {
30009
+ if (graph.formulas.has(ref)) {
30010
+ visit(ref);
30011
+ }
30012
+ }
30013
+ order.push(label);
30014
+ };
30015
+ for (const label of graph.formulas.keys()) {
30016
+ visit(label);
30017
+ }
30018
+ return { order, circular };
30019
+ }
30020
+ function evaluateBasicTableFormula(formula, getCellValue) {
30021
+ const normalized = normalizeTableFormula(formula);
30022
+ if (!normalized) {
30023
+ return { value: null, error: "empty" };
30024
+ }
30025
+ const tokens = tokenizeFormula(normalized);
30026
+ if (!tokens) {
30027
+ return { value: null, error: "invalid-formula" };
30028
+ }
30029
+ const parser = new FormulaParser(tokens, getCellValue);
30030
+ const result = parser.parseExpression();
30031
+ if (result.error) {
30032
+ return result;
30033
+ }
30034
+ if (!parser.isComplete()) {
30035
+ return { value: null, error: "invalid-formula" };
30036
+ }
30037
+ return result;
30038
+ }
30039
+ function tokenizeFormula(formula) {
30040
+ const tokens = [];
30041
+ let index = 0;
30042
+ while (index < formula.length) {
30043
+ const char = formula[index];
30044
+ if (!char) break;
30045
+ if (/\s/.test(char)) {
30046
+ index += 1;
30047
+ continue;
30048
+ }
30049
+ if (char === "," || char === "+" || char === "-" || char === "*" || char === "/" || char === "(" || char === ")") {
30050
+ if (char === ",") tokens.push({ type: "comma", value: char });
30051
+ else if (char === "(" || char === ")") tokens.push({ type: "paren", value: char });
30052
+ else tokens.push({ type: "operator", value: char });
30053
+ index += 1;
30054
+ continue;
30055
+ }
30056
+ const numberMatch = formula.slice(index).match(/^\d+(?:\.\d+)?/);
30057
+ if (numberMatch?.[0]) {
30058
+ tokens.push({ type: "number", value: Number.parseFloat(numberMatch[0]) });
30059
+ index += numberMatch[0].length;
30060
+ continue;
30061
+ }
30062
+ const identifierMatch = formula.slice(index).match(/^[A-Z]+[1-9]\d*(?::[A-Z]+[1-9]\d*)?|^[A-Z]+/i);
30063
+ if (identifierMatch?.[0]) {
30064
+ const value = identifierMatch[0].toUpperCase();
30065
+ if (CELL_RANGE_RE.test(value)) tokens.push({ type: "range", value });
30066
+ else if (parseTableCellAddress(value)) tokens.push({ type: "cell", value });
30067
+ else if (SUPPORTED_FUNCTIONS.has(value)) tokens.push({ type: "function", value });
30068
+ else return null;
30069
+ index += identifierMatch[0].length;
30070
+ continue;
30071
+ }
30072
+ return null;
30073
+ }
30074
+ return tokens;
30075
+ }
30076
+ var FormulaParser = class {
30077
+ constructor(tokens, getCellValue) {
30078
+ this.tokens = tokens;
30079
+ this.getCellValue = getCellValue;
30080
+ this.index = 0;
30081
+ }
30082
+ isComplete() {
30083
+ return this.index >= this.tokens.length;
30084
+ }
30085
+ parseExpression() {
30086
+ let left = this.parseTerm();
30087
+ while (!left.error) {
30088
+ const operator = this.peekOperator(["+", "-"]);
30089
+ if (!operator) break;
30090
+ this.index += 1;
30091
+ const right = this.parseTerm();
30092
+ if (right.error) return right;
30093
+ left = {
30094
+ value: operator.value === "+" ? left.value + right.value : left.value - right.value,
30095
+ error: null
30096
+ };
30097
+ }
30098
+ return left;
30099
+ }
30100
+ parseTerm() {
30101
+ let left = this.parseFactor();
30102
+ while (!left.error) {
30103
+ const operator = this.peekOperator(["*", "/"]);
30104
+ if (!operator) break;
30105
+ this.index += 1;
30106
+ const right = this.parseFactor();
30107
+ if (right.error) return right;
30108
+ if (operator.value === "/" && right.value === 0) {
30109
+ return { value: null, error: "division-by-zero" };
30110
+ }
30111
+ left = {
30112
+ value: operator.value === "*" ? left.value * right.value : left.value / right.value,
30113
+ error: null
30114
+ };
30115
+ }
30116
+ return left;
30117
+ }
30118
+ parseFactor() {
30119
+ const token = this.tokens[this.index];
30120
+ if (!token) {
30121
+ return { value: null, error: "invalid-formula" };
30122
+ }
30123
+ if (token.type === "operator" && token.value === "-") {
30124
+ this.index += 1;
30125
+ const value = this.parseFactor();
30126
+ if (value.error) return value;
30127
+ return { value: -value.value, error: null };
30128
+ }
30129
+ if (token.type === "number") {
30130
+ this.index += 1;
30131
+ return { value: token.value, error: null };
30132
+ }
30133
+ if (token.type === "cell") {
30134
+ this.index += 1;
30135
+ return this.readCellNumber(token.value);
30136
+ }
30137
+ if (token.type === "function") {
30138
+ return this.parseFunction(token.value);
30139
+ }
30140
+ if (token.type === "paren" && token.value === "(") {
30141
+ this.index += 1;
30142
+ const value = this.parseExpression();
30143
+ if (value.error) return value;
30144
+ if (!this.consumeParen(")")) {
30145
+ return { value: null, error: "invalid-formula" };
30146
+ }
30147
+ return value;
30148
+ }
30149
+ return { value: null, error: "invalid-formula" };
30150
+ }
30151
+ parseFunction(name) {
30152
+ this.index += 1;
30153
+ if (!this.consumeParen("(")) {
30154
+ return { value: null, error: "invalid-formula" };
30155
+ }
30156
+ const values = [];
30157
+ while (true) {
30158
+ const token = this.tokens[this.index];
30159
+ if (!token) {
30160
+ return { value: null, error: "invalid-formula" };
30161
+ }
30162
+ if (token.type === "range") {
30163
+ this.index += 1;
30164
+ const range = parseTableCellRange(token.value);
30165
+ if (!range) return { value: null, error: "invalid-reference" };
30166
+ for (const label of getTableCellRangeLabels(range)) {
30167
+ const cellValue = this.readCellNumber(label);
30168
+ if (cellValue.error) return cellValue;
30169
+ values.push(cellValue.value);
30170
+ }
30171
+ } else {
30172
+ const value = this.parseExpression();
30173
+ if (value.error) return value;
30174
+ values.push(value.value);
30175
+ }
30176
+ if (this.consumeComma()) {
30177
+ continue;
30178
+ }
30179
+ if (this.consumeParen(")")) {
30180
+ break;
30181
+ }
30182
+ return { value: null, error: "invalid-formula" };
30183
+ }
30184
+ if (values.length === 0) {
30185
+ return { value: null, error: "invalid-formula" };
30186
+ }
30187
+ if (name === "SUM") return { value: values.reduce((sum, value) => sum + value, 0), error: null };
30188
+ if (name === "AVG") return { value: values.reduce((sum, value) => sum + value, 0) / values.length, error: null };
30189
+ if (name === "MIN") return { value: Math.min(...values), error: null };
30190
+ if (name === "MAX") return { value: Math.max(...values), error: null };
30191
+ if (name === "COUNT") return { value: values.length, error: null };
30192
+ return { value: null, error: "invalid-formula" };
30193
+ }
30194
+ readCellNumber(label) {
30195
+ const value = this.getCellValue(label);
30196
+ const parsed = typeof value === "number" ? value : Number.parseFloat(String(value ?? "").trim());
30197
+ if (!Number.isFinite(parsed)) {
30198
+ return { value: null, error: "invalid-reference" };
30199
+ }
30200
+ return { value: parsed, error: null };
30201
+ }
30202
+ peekOperator(operators) {
30203
+ const token = this.tokens[this.index];
30204
+ return token?.type === "operator" && operators.includes(token.value) ? token : null;
30205
+ }
30206
+ consumeComma() {
30207
+ if (this.tokens[this.index]?.type !== "comma") {
30208
+ return false;
30209
+ }
30210
+ this.index += 1;
30211
+ return true;
30212
+ }
30213
+ consumeParen(value) {
30214
+ const token = this.tokens[this.index];
30215
+ if (token?.type !== "paren" || token.value !== value) {
30216
+ return false;
30217
+ }
30218
+ this.index += 1;
30219
+ return true;
30220
+ }
30221
+ };
30222
+
30223
+ // src/components/UEditor/table-formula-commands.ts
30224
+ var UEDITOR_TABLE_FORMULA_RECALCULATE_META = "ueditorTableFormulaRecalculate";
30225
+ function collectChildren2(node) {
30226
+ const children = [];
30227
+ node.forEach((child) => children.push(child));
30228
+ return children;
30229
+ }
30230
+ function getTableRows2(tableNode) {
30231
+ const rows = [];
30232
+ tableNode.forEach((rowNode, rowOffset) => {
30233
+ const cells = [];
30234
+ rowNode.forEach((cellNode, cellOffset, index) => {
30235
+ cells.push({
30236
+ index,
30237
+ node: cellNode,
30238
+ relativePos: rowOffset + 1 + cellOffset
30239
+ });
30240
+ });
30241
+ rows.push({ node: rowNode, cells });
30242
+ });
30243
+ return rows;
30244
+ }
30245
+ function safeFindCell2(map, relativePos) {
30246
+ try {
30247
+ return map.findCell(relativePos);
30248
+ } catch {
30249
+ return null;
30250
+ }
30251
+ }
30252
+ function getCellText(cellNode) {
30253
+ return cellNode.textBetween(0, cellNode.content.size, "\n").trim();
30254
+ }
30255
+ function createCellDisplayContent(cellNode, displayValue) {
30256
+ const paragraphType = cellNode.type.schema.nodes.paragraph;
30257
+ if (!paragraphType || !displayValue) {
30258
+ return paragraphType ? [paragraphType.create()] : cellNode.content;
30259
+ }
30260
+ return [paragraphType.create(null, cellNode.type.schema.text(displayValue))];
30261
+ }
30262
+ function buildTableValueGetter(tableNode) {
30263
+ const map = TableMap2.get(tableNode);
30264
+ const values = /* @__PURE__ */ new Map();
30265
+ for (const rowInfo of getTableRows2(tableNode)) {
30266
+ for (const entry of rowInfo.cells) {
30267
+ const rect = safeFindCell2(map, entry.relativePos);
30268
+ if (!rect) continue;
30269
+ const label = `${indexToColumnName(rect.left)}${rect.top + 1}`;
30270
+ const computedValue = entry.node.attrs.computedValue;
30271
+ values.set(label, typeof computedValue === "string" && computedValue.trim() ? computedValue : getCellText(entry.node));
30272
+ }
30273
+ }
30274
+ return (label) => values.get(label.toUpperCase());
30275
+ }
30276
+ function buildTableValueMap(tableNode) {
30277
+ const map = TableMap2.get(tableNode);
30278
+ const values = /* @__PURE__ */ new Map();
30279
+ for (const rowInfo of getTableRows2(tableNode)) {
30280
+ for (const entry of rowInfo.cells) {
30281
+ const rect = safeFindCell2(map, entry.relativePos);
30282
+ if (!rect) continue;
30283
+ const label = `${indexToColumnName(rect.left)}${rect.top + 1}`;
30284
+ const computedValue = entry.node.attrs.computedValue;
30285
+ values.set(label, typeof computedValue === "string" && computedValue.trim() ? computedValue : getCellText(entry.node));
30286
+ }
30287
+ }
30288
+ return values;
30289
+ }
30290
+ function getFormulaComputedValue(formula, tableNode) {
30291
+ const result = evaluateBasicTableFormula(formula, buildTableValueGetter(tableNode));
30292
+ return result.error ? `#${result.error.toUpperCase()}` : String(result.value);
30293
+ }
30294
+ function normalizeFormulaInput(formula) {
30295
+ const trimmed = formula.trim();
30296
+ if (!trimmed) return "";
30297
+ return trimmed.startsWith("=") ? trimmed : `=${trimmed}`;
30298
+ }
30299
+ function setSelectedTableCellFormula(editor, formula) {
30300
+ const normalized = normalizeFormulaInput(formula);
30301
+ const { state, view } = editor;
30302
+ if (!normalized) {
30303
+ const clearedFormula = setCellAttr("formula", null)(state, view.dispatch.bind(view));
30304
+ const clearedValue = setCellAttr("computedValue", null)(editor.state, view.dispatch.bind(view));
30305
+ if (clearedFormula || clearedValue) {
30306
+ view.focus();
30307
+ dispatchTableLayoutChange(editor);
30308
+ return true;
30309
+ }
30310
+ return false;
30311
+ }
30312
+ const rect = selectedRect2(state);
30313
+ const computedValue = getFormulaComputedValue(normalized, rect.table);
30314
+ const appliedFormula = setCellAttr("formula", normalized)(state, view.dispatch.bind(view));
30315
+ const appliedValue = setCellAttr("computedValue", computedValue)(editor.state, view.dispatch.bind(view));
30316
+ if (appliedFormula || appliedValue) {
30317
+ view.focus();
30318
+ dispatchTableLayoutChange(editor);
30319
+ return true;
30320
+ }
30321
+ return false;
30322
+ }
30323
+ function clearSelectedTableCellFormula(editor) {
30324
+ return setSelectedTableCellFormula(editor, "");
30325
+ }
30326
+ function promoteFormulaTextInTableNode(tableNode) {
30327
+ let changed = false;
30328
+ const rows = getTableRows2(tableNode).map((rowInfo) => {
30329
+ const cells = collectChildren2(rowInfo.node);
30330
+ for (const entry of rowInfo.cells) {
30331
+ const text = getCellText(entry.node);
30332
+ const formula = text.startsWith("=") ? normalizeFormulaInput(text) : "";
30333
+ if (!formula || entry.node.attrs.formula === formula) continue;
30334
+ cells[entry.index] = entry.node.type.create(
30335
+ {
30336
+ ...entry.node.attrs,
30337
+ formula,
30338
+ computedValue: null
30339
+ },
30340
+ entry.node.content,
30341
+ entry.node.marks
30342
+ );
30343
+ changed = true;
30344
+ }
30345
+ return rowInfo.node.type.create(rowInfo.node.attrs, cells);
30346
+ });
30347
+ return {
30348
+ tableNode: changed ? tableNode.type.create(tableNode.attrs, rows) : tableNode,
30349
+ changed
30350
+ };
30351
+ }
30352
+ function recalculateTableNode(tableNode) {
30353
+ const promoted = promoteFormulaTextInTableNode(tableNode);
30354
+ tableNode = promoted.tableNode;
30355
+ const map = TableMap2.get(tableNode);
30356
+ const values = buildTableValueMap(tableNode);
30357
+ const formulaEntries = /* @__PURE__ */ new Map();
30358
+ let changed = false;
30359
+ const rowInfos = getTableRows2(tableNode);
30360
+ for (const [rowIndex, rowInfo] of rowInfos.entries()) {
30361
+ for (const entry of rowInfo.cells) {
30362
+ const formula = typeof entry.node.attrs.formula === "string" ? entry.node.attrs.formula.trim() : "";
30363
+ if (!formula) continue;
30364
+ const rectForCell = safeFindCell2(map, entry.relativePos);
30365
+ if (!rectForCell) continue;
30366
+ const label = `${indexToColumnName(rectForCell.left)}${rectForCell.top + 1}`;
30367
+ formulaEntries.set(label, {
30368
+ rowIndex,
30369
+ cellIndex: entry.index,
30370
+ node: entry.node,
30371
+ formula
30372
+ });
30373
+ }
30374
+ }
30375
+ const graph = buildTableFormulaDependencyGraph(
30376
+ Array.from(formulaEntries, ([label, entry]) => ({
30377
+ label,
30378
+ formula: entry.formula
30379
+ }))
30380
+ );
30381
+ const { order, circular } = getTableFormulaRecalculationOrder(graph);
30382
+ const computedValues = /* @__PURE__ */ new Map();
30383
+ const getCellValue = (label) => values.get(label.toUpperCase());
30384
+ for (const label of circular) {
30385
+ computedValues.set(label, formatFormulaError("circular-reference"));
30386
+ values.set(label, formatFormulaError("circular-reference"));
30387
+ }
30388
+ for (const label of order) {
30389
+ const entry = formulaEntries.get(label);
30390
+ if (!entry) continue;
30391
+ const result = evaluateBasicTableFormula(entry.formula, getCellValue);
30392
+ const computedValue = result.error ? formatFormulaError(result.error) : String(result.value);
30393
+ computedValues.set(label, computedValue);
30394
+ values.set(label, computedValue);
30395
+ }
30396
+ const rows = rowInfos.map((rowInfo) => {
30397
+ const cells = collectChildren2(rowInfo.node);
30398
+ for (const entry of rowInfo.cells) {
30399
+ const rectForCell = safeFindCell2(map, entry.relativePos);
30400
+ if (!rectForCell) continue;
30401
+ const label = `${indexToColumnName(rectForCell.left)}${rectForCell.top + 1}`;
30402
+ const computedValue = computedValues.get(label);
30403
+ if (computedValue == null) continue;
30404
+ const contentMatchesComputedValue = getCellText(entry.node) === computedValue;
30405
+ if (entry.node.attrs.computedValue === computedValue && contentMatchesComputedValue) continue;
30406
+ cells[entry.index] = entry.node.type.create(
30407
+ {
30408
+ ...entry.node.attrs,
30409
+ computedValue
30410
+ },
30411
+ createCellDisplayContent(entry.node, computedValue),
30412
+ entry.node.marks
30413
+ );
30414
+ changed = true;
30415
+ }
30416
+ return rowInfo.node.type.create(rowInfo.node.attrs, cells);
30417
+ });
30418
+ if (!changed) return promoted.changed ? tableNode : null;
30419
+ return tableNode.type.create(tableNode.attrs, rows);
30420
+ }
30421
+ function recalculateSelectedTable(editor) {
30422
+ const rect = selectedRect2(editor.state);
30423
+ const tableNode = rect.table;
30424
+ const nextTable = recalculateTableNode(tableNode);
30425
+ if (!nextTable) return false;
30426
+ editor.view.dispatch(
30427
+ editor.state.tr.replaceWith(rect.tableStart - 1, rect.tableStart - 1 + tableNode.nodeSize, nextTable).setMeta(UEDITOR_TABLE_FORMULA_RECALCULATE_META, true)
30428
+ );
30429
+ dispatchTableLayoutChange(editor);
30430
+ return true;
30431
+ }
30432
+ function recalculateAllTableFormulas(editor) {
30433
+ const replacements = [];
30434
+ editor.state.doc.descendants((node, pos) => {
30435
+ if (node.type.name !== "table") return true;
30436
+ const nextTable = recalculateTableNode(node);
30437
+ if (nextTable) {
30438
+ replacements.push({
30439
+ from: pos,
30440
+ to: pos + node.nodeSize,
30441
+ node: nextTable
30442
+ });
30443
+ }
30444
+ return false;
30445
+ });
30446
+ if (replacements.length === 0) return false;
30447
+ let tr = editor.state.tr;
30448
+ for (const replacement of replacements.reverse()) {
30449
+ tr = tr.replaceWith(replacement.from, replacement.to, replacement.node);
30450
+ }
30451
+ editor.view.dispatch(tr.setMeta(UEDITOR_TABLE_FORMULA_RECALCULATE_META, true));
30452
+ dispatchTableLayoutChange(editor);
30453
+ return true;
30454
+ }
30455
+
30456
+ // src/components/UEditor/menus.tsx
29705
30457
  import { Fragment as Fragment29, jsx as jsx86, jsxs as jsxs72 } from "react/jsx-runtime";
29706
30458
  function applyTableCellBackground(editor, color) {
29707
30459
  const value = color || null;
29708
30460
  const { state, view } = editor;
29709
- const applied = setCellAttr("backgroundColor", value)(state, view.dispatch.bind(view));
30461
+ const applied = setCellAttr2("backgroundColor", value)(state, view.dispatch.bind(view));
29710
30462
  if (applied) {
29711
30463
  view.focus();
29712
30464
  return;
@@ -29732,6 +30484,8 @@ var BubbleMenuContent = ({
29732
30484
  setShowLinkInput(initialShowLinkInput);
29733
30485
  }, [initialShowLinkInput]);
29734
30486
  const [showTypographyPanel, setShowTypographyPanel] = useState48(false);
30487
+ const [showFormulaPanel, setShowFormulaPanel] = useState48(false);
30488
+ const [formulaDraft, setFormulaDraft] = useState48("");
29735
30489
  const [showFontSizeOptions, setShowFontSizeOptions] = useState48(false);
29736
30490
  const [fontSizeDraft, setFontSizeDraft] = useState48("");
29737
30491
  const isImageSelected = editor.isActive("image");
@@ -29742,6 +30496,7 @@ var BubbleMenuContent = ({
29742
30496
  const currentTextColor = normalizeStyleValue(textStyleAttrs.color) || "inherit";
29743
30497
  const currentHighlightColor = normalizeStyleValue(editor.getAttributes("highlight").color) || "";
29744
30498
  const currentCellBgColor = normalizeStyleValue(editor.getAttributes("tableCell").backgroundColor || editor.getAttributes("tableHeader").backgroundColor) || "";
30499
+ const currentCellFormula = normalizeStyleValue(editor.getAttributes("tableCell").formula || editor.getAttributes("tableHeader").formula) || "";
29745
30500
  const isInTable2 = isSelectionInTable(editor.state);
29746
30501
  const canMergeCells = isInTable2 && editor.can().mergeCells();
29747
30502
  const canSplitCell = isInTable2 && editor.can().splitCell();
@@ -29758,6 +30513,9 @@ var BubbleMenuContent = ({
29758
30513
  useEffect36(() => {
29759
30514
  setFontSizeDraft(currentFontSize.replace(/px$/i, ""));
29760
30515
  }, [currentFontSize]);
30516
+ useEffect36(() => {
30517
+ setFormulaDraft(currentCellFormula);
30518
+ }, [currentCellFormula]);
29761
30519
  const applyFontSizeDraft = () => {
29762
30520
  const normalized = fontSizeDraft.trim();
29763
30521
  if (!normalized) {
@@ -29960,6 +30718,82 @@ var BubbleMenuContent = ({
29960
30718
  )
29961
30719
  ] });
29962
30720
  }
30721
+ if (showFormulaPanel && isInTable2) {
30722
+ return /* @__PURE__ */ jsxs72("div", { className: "w-72 p-2", children: [
30723
+ /* @__PURE__ */ jsxs72("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
30724
+ /* @__PURE__ */ jsx86("span", { className: "px-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: t("tableMenu.formula") || "Formula" }),
30725
+ /* @__PURE__ */ jsx86(
30726
+ "button",
30727
+ {
30728
+ type: "button",
30729
+ onClick: () => {
30730
+ setShowFormulaPanel(false);
30731
+ onKeepOpenChange?.(false);
30732
+ },
30733
+ className: "rounded-md px-2 py-1 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
30734
+ children: t("colors.done")
30735
+ }
30736
+ )
30737
+ ] }),
30738
+ /* @__PURE__ */ jsx86("div", { className: "flex h-9 items-center overflow-hidden rounded-md border border-border/60 bg-muted/40", children: /* @__PURE__ */ jsx86(
30739
+ "input",
30740
+ {
30741
+ value: formulaDraft,
30742
+ onChange: (event) => setFormulaDraft(event.target.value),
30743
+ onMouseDown: (event) => event.stopPropagation(),
30744
+ onClick: (event) => event.stopPropagation(),
30745
+ onKeyDown: (event) => {
30746
+ event.stopPropagation();
30747
+ if (event.key === "Enter") {
30748
+ event.preventDefault();
30749
+ setSelectedTableCellFormula(editor, formulaDraft);
30750
+ setShowFormulaPanel(false);
30751
+ onKeepOpenChange?.(false);
30752
+ }
30753
+ },
30754
+ "aria-label": t("tableMenu.formula") || "Formula",
30755
+ placeholder: "=SUM(A1:A3)",
30756
+ className: "h-full min-w-0 flex-1 bg-transparent px-2 text-sm font-medium text-foreground outline-none"
30757
+ }
30758
+ ) }),
30759
+ /* @__PURE__ */ jsxs72("div", { className: "mt-2 grid grid-cols-3 gap-1", children: [
30760
+ /* @__PURE__ */ jsx86(
30761
+ "button",
30762
+ {
30763
+ type: "button",
30764
+ onClick: () => {
30765
+ setSelectedTableCellFormula(editor, formulaDraft);
30766
+ setShowFormulaPanel(false);
30767
+ onKeepOpenChange?.(false);
30768
+ },
30769
+ className: "h-8 rounded-md bg-primary/10 text-xs font-semibold text-primary transition-colors hover:bg-primary/15",
30770
+ children: t("tableMenu.apply") || "Apply"
30771
+ }
30772
+ ),
30773
+ /* @__PURE__ */ jsx86(
30774
+ "button",
30775
+ {
30776
+ type: "button",
30777
+ onClick: () => {
30778
+ clearSelectedTableCellFormula(editor);
30779
+ setFormulaDraft("");
30780
+ },
30781
+ className: "h-8 rounded-md bg-muted/40 text-xs font-semibold text-foreground transition-colors hover:bg-muted",
30782
+ children: t("tableMenu.clear") || "Clear"
30783
+ }
30784
+ ),
30785
+ /* @__PURE__ */ jsx86(
30786
+ "button",
30787
+ {
30788
+ type: "button",
30789
+ onClick: () => recalculateSelectedTable(editor),
30790
+ className: "h-8 rounded-md bg-muted/40 text-xs font-semibold text-foreground transition-colors hover:bg-muted",
30791
+ children: t("tableMenu.recalculate") || "Recalc"
30792
+ }
30793
+ )
30794
+ ] })
30795
+ ] });
30796
+ }
29963
30797
  if (showTypographyPanel) {
29964
30798
  return /* @__PURE__ */ jsxs72("div", { className: "w-72 p-2", children: [
29965
30799
  /* @__PURE__ */ jsxs72("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
@@ -30173,6 +31007,21 @@ var BubbleMenuContent = ({
30173
31007
  )
30174
31008
  }
30175
31009
  ),
31010
+ /* @__PURE__ */ jsx86(
31011
+ ToolbarButton,
31012
+ {
31013
+ onMouseDown: () => {
31014
+ onKeepOpenChange?.(true);
31015
+ },
31016
+ onClick: () => {
31017
+ setFormulaDraft(currentCellFormula);
31018
+ setShowFormulaPanel(true);
31019
+ },
31020
+ active: Boolean(currentCellFormula),
31021
+ title: t("tableMenu.formula") || "Formula",
31022
+ children: /* @__PURE__ */ jsx86(Sigma, { className: "w-4 h-4" })
31023
+ }
31024
+ ),
30176
31025
  /* @__PURE__ */ jsx86(
30177
31026
  ToolbarButton,
30178
31027
  {
@@ -34014,7 +34863,7 @@ if (typeof WeakMap != "undefined") {
34014
34863
  return cache[cachePos++] = value;
34015
34864
  };
34016
34865
  }
34017
- var TableMap2 = class {
34866
+ var TableMap3 = class {
34018
34867
  constructor(width, height, map, problems) {
34019
34868
  this.width = width;
34020
34869
  this.height = height;
@@ -34151,7 +35000,7 @@ function computeMap(table) {
34151
35000
  pos++;
34152
35001
  }
34153
35002
  if (width === 0 || height === 0) (problems || (problems = [])).push({ type: "zero_sized" });
34154
- const tableMap = new TableMap2(width, height, map, problems);
35003
+ const tableMap = new TableMap3(width, height, map, problems);
34155
35004
  let badWidths = false;
34156
35005
  for (let i = 0; !badWidths && i < colWidths.length; i += 2) if (colWidths[i] != null && colWidths[i + 1] < height) badWidths = true;
34157
35006
  if (badWidths) findBadColWidths(tableMap, colWidths, table);
@@ -34255,7 +35104,7 @@ function inSameTable($cellA, $cellB) {
34255
35104
  }
34256
35105
  function nextCell($pos, axis, dir) {
34257
35106
  const table = $pos.node(-1);
34258
- const map = TableMap2.get(table);
35107
+ const map = TableMap3.get(table);
34259
35108
  const tableStart = $pos.start(-1);
34260
35109
  const moved = map.nextCell($pos.pos - tableStart, axis, dir);
34261
35110
  return moved == null ? null : $pos.node(0).resolve(tableStart + moved);
@@ -34275,7 +35124,7 @@ function removeColSpan(attrs, pos, n = 1) {
34275
35124
  var CellSelection = class CellSelection2 extends Selection {
34276
35125
  constructor($anchorCell, $headCell = $anchorCell) {
34277
35126
  const table = $anchorCell.node(-1);
34278
- const map = TableMap2.get(table);
35127
+ const map = TableMap3.get(table);
34279
35128
  const tableStart = $anchorCell.start(-1);
34280
35129
  const rect = map.rectBetween($anchorCell.pos - tableStart, $headCell.pos - tableStart);
34281
35130
  const doc = $anchorCell.node(0);
@@ -34304,7 +35153,7 @@ var CellSelection = class CellSelection2 extends Selection {
34304
35153
  }
34305
35154
  content() {
34306
35155
  const table = this.$anchorCell.node(-1);
34307
- const map = TableMap2.get(table);
35156
+ const map = TableMap3.get(table);
34308
35157
  const tableStart = this.$anchorCell.start(-1);
34309
35158
  const rect = map.rectBetween(this.$anchorCell.pos - tableStart, this.$headCell.pos - tableStart);
34310
35159
  const seen = {};
@@ -34358,7 +35207,7 @@ var CellSelection = class CellSelection2 extends Selection {
34358
35207
  }
34359
35208
  forEachCell(f) {
34360
35209
  const table = this.$anchorCell.node(-1);
34361
- const map = TableMap2.get(table);
35210
+ const map = TableMap3.get(table);
34362
35211
  const tableStart = this.$anchorCell.start(-1);
34363
35212
  const cells = map.cellsInRect(map.rectBetween(this.$anchorCell.pos - tableStart, this.$headCell.pos - tableStart));
34364
35213
  for (let i = 0; i < cells.length; i++) f(table.nodeAt(cells[i]), tableStart + cells[i]);
@@ -34373,7 +35222,7 @@ var CellSelection = class CellSelection2 extends Selection {
34373
35222
  }
34374
35223
  static colSelection($anchorCell, $headCell = $anchorCell) {
34375
35224
  const table = $anchorCell.node(-1);
34376
- const map = TableMap2.get(table);
35225
+ const map = TableMap3.get(table);
34377
35226
  const tableStart = $anchorCell.start(-1);
34378
35227
  const anchorRect = map.findCell($anchorCell.pos - tableStart);
34379
35228
  const headRect = map.findCell($headCell.pos - tableStart);
@@ -34389,7 +35238,7 @@ var CellSelection = class CellSelection2 extends Selection {
34389
35238
  }
34390
35239
  isRowSelection() {
34391
35240
  const table = this.$anchorCell.node(-1);
34392
- const map = TableMap2.get(table);
35241
+ const map = TableMap3.get(table);
34393
35242
  const tableStart = this.$anchorCell.start(-1);
34394
35243
  const anchorLeft = map.colCount(this.$anchorCell.pos - tableStart);
34395
35244
  const headLeft = map.colCount(this.$headCell.pos - tableStart);
@@ -34403,7 +35252,7 @@ var CellSelection = class CellSelection2 extends Selection {
34403
35252
  }
34404
35253
  static rowSelection($anchorCell, $headCell = $anchorCell) {
34405
35254
  const table = $anchorCell.node(-1);
34406
- const map = TableMap2.get(table);
35255
+ const map = TableMap3.get(table);
34407
35256
  const tableStart = $anchorCell.start(-1);
34408
35257
  const anchorRect = map.findCell($anchorCell.pos - tableStart);
34409
35258
  const headRect = map.findCell($headCell.pos - tableStart);
@@ -34452,7 +35301,7 @@ var CellBookmark = class CellBookmark2 {
34452
35301
  };
34453
35302
  var fixTablesKey = new PluginKey4("fix-tables");
34454
35303
  function convertTableNodeToArrayOfRows(tableNode) {
34455
- const map = TableMap2.get(tableNode);
35304
+ const map = TableMap3.get(tableNode);
34456
35305
  const rows = [];
34457
35306
  const rowCount = map.height;
34458
35307
  const colCount$1 = map.width;
@@ -34483,7 +35332,7 @@ function convertTableNodeToArrayOfRows(tableNode) {
34483
35332
  }
34484
35333
  function convertArrayOfRowsToTableNode(tableNode, arrayOfNodes) {
34485
35334
  const newRows = [];
34486
- const map = TableMap2.get(tableNode);
35335
+ const map = TableMap3.get(tableNode);
34487
35336
  const rowCount = map.height;
34488
35337
  const colCount$1 = map.width;
34489
35338
  for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
@@ -34532,7 +35381,7 @@ function findParentNode(predicate, $pos) {
34532
35381
  function getCellsInColumn(columnIndex, selection) {
34533
35382
  const table = findTable(selection.$from);
34534
35383
  if (!table) return;
34535
- const map = TableMap2.get(table.node);
35384
+ const map = TableMap3.get(table.node);
34536
35385
  if (columnIndex < 0 || columnIndex > map.width - 1) return;
34537
35386
  return map.cellsInRect({
34538
35387
  left: columnIndex,
@@ -34553,7 +35402,7 @@ function getCellsInColumn(columnIndex, selection) {
34553
35402
  function getCellsInRow(rowIndex, selection) {
34554
35403
  const table = findTable(selection.$from);
34555
35404
  if (!table) return;
34556
- const map = TableMap2.get(table.node);
35405
+ const map = TableMap3.get(table.node);
34557
35406
  if (rowIndex < 0 || rowIndex > map.height - 1) return;
34558
35407
  return map.cellsInRect({
34559
35408
  left: 0,
@@ -34682,7 +35531,7 @@ function moveColumn(moveColParams) {
34682
35531
  const newTable = moveTableColumn$1(table.node, indexesOriginColumn, indexesTargetColumn, 0);
34683
35532
  tr.replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
34684
35533
  if (!select) return true;
34685
- const map = TableMap2.get(newTable);
35534
+ const map = TableMap3.get(newTable);
34686
35535
  const start = table.start;
34687
35536
  const index = targetIndex;
34688
35537
  const lastCell = map.positionAt(map.height - 1, index, newTable);
@@ -34710,7 +35559,7 @@ function moveRow(moveRowParams) {
34710
35559
  const newTable = moveTableRow$1(table.node, indexesOriginRow, indexesTargetRow, 0);
34711
35560
  tr.replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
34712
35561
  if (!select) return true;
34713
- const map = TableMap2.get(newTable);
35562
+ const map = TableMap3.get(newTable);
34714
35563
  const start = table.start;
34715
35564
  const index = targetIndex;
34716
35565
  const lastCell = map.positionAt(index, map.width - 1, newTable);
@@ -34725,12 +35574,12 @@ function moveTableRow$1(table, indexesOrigin, indexesTarget, direction) {
34725
35574
  rows = moveRowInArrayOfRows(rows, indexesOrigin, indexesTarget, direction);
34726
35575
  return convertArrayOfRowsToTableNode(table, rows);
34727
35576
  }
34728
- function selectedRect2(state) {
35577
+ function selectedRect3(state) {
34729
35578
  const sel = state.selection;
34730
35579
  const $pos = selectionCell(state);
34731
35580
  const table = $pos.node(-1);
34732
35581
  const tableStart = $pos.start(-1);
34733
- const map = TableMap2.get(table);
35582
+ const map = TableMap3.get(table);
34734
35583
  return {
34735
35584
  ...sel instanceof CellSelection ? map.rectBetween(sel.$anchorCell.pos - tableStart, sel.$headCell.pos - tableStart) : map.findCell($pos.pos - tableStart),
34736
35585
  tableStart,
@@ -34743,7 +35592,7 @@ function deprecated_toggleHeader(type) {
34743
35592
  if (!isInTable(state)) return false;
34744
35593
  if (dispatch) {
34745
35594
  const types = tableNodeTypes(state.schema);
34746
- const rect = selectedRect2(state), tr = state.tr;
35595
+ const rect = selectedRect3(state), tr = state.tr;
34747
35596
  const cells = rect.map.cellsInRect(type == "column" ? {
34748
35597
  left: rect.left,
34749
35598
  top: 0,
@@ -34783,7 +35632,7 @@ function toggleHeader(type, options) {
34783
35632
  if (!isInTable(state)) return false;
34784
35633
  if (dispatch) {
34785
35634
  const types = tableNodeTypes(state.schema);
34786
- const rect = selectedRect2(state), tr = state.tr;
35635
+ const rect = selectedRect3(state), tr = state.tr;
34787
35636
  const isHeaderRowEnabled = isHeaderEnabledByType("row", rect, types);
34788
35637
  const isHeaderColumnEnabled = isHeaderEnabledByType("column", rect, types);
34789
35638
  const selectionStartsAt = (type === "column" ? isHeaderRowEnabled : type === "row" ? isHeaderColumnEnabled : false) ? 1 : 0;
@@ -34944,6 +35793,262 @@ import {
34944
35793
  Trash2 as Trash24
34945
35794
  } from "lucide-react";
34946
35795
 
35796
+ // src/components/UEditor/table-layout-model.ts
35797
+ var FALLBACK_TABLE_ROW_HEIGHT = 44;
35798
+ var FALLBACK_TABLE_COLUMN_WIDTH = 160;
35799
+ function getVisibleTableBounds(layout) {
35800
+ const left = Math.max(layout.tableLeft, layout.wrapperLeft);
35801
+ const top = Math.max(layout.tableTop, layout.wrapperTop);
35802
+ const right = Math.min(layout.tableLeft + layout.tableWidth, layout.wrapperLeft + layout.viewportWidth);
35803
+ const bottom = Math.min(layout.tableTop + layout.tableHeight, layout.wrapperTop + layout.viewportHeight);
35804
+ return {
35805
+ left,
35806
+ top,
35807
+ right,
35808
+ bottom,
35809
+ width: Math.max(0, right - left),
35810
+ height: Math.max(0, bottom - top)
35811
+ };
35812
+ }
35813
+ function metricOrFallback(value, fallback) {
35814
+ return Number.isFinite(value) && value > 0 ? value : fallback;
35815
+ }
35816
+ function parsePixelMetric(value) {
35817
+ if (!value) return null;
35818
+ const parsed = Number.parseFloat(value);
35819
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
35820
+ }
35821
+ function getPrimaryCell(table) {
35822
+ const cell = table.querySelector("th,td");
35823
+ return cell instanceof HTMLTableCellElement ? cell : null;
35824
+ }
35825
+ function getLastCell(table) {
35826
+ const lastRow = table.rows.item(table.rows.length - 1);
35827
+ if (!(lastRow instanceof HTMLTableRowElement)) return null;
35828
+ const cell = lastRow.cells.item(lastRow.cells.length - 1);
35829
+ return cell instanceof HTMLTableCellElement ? cell : null;
35830
+ }
35831
+ function getCellFromTarget(target) {
35832
+ const element = resolveEventElement(target);
35833
+ if (!element) return null;
35834
+ const directCell = element.closest("th,td");
35835
+ if (directCell instanceof HTMLTableCellElement) {
35836
+ return directCell;
35837
+ }
35838
+ const table = element.closest("table");
35839
+ if (table instanceof HTMLTableElement) {
35840
+ return getPrimaryCell(table);
35841
+ }
35842
+ return null;
35843
+ }
35844
+ function findTableInfo(editor, pos) {
35845
+ const $pos = editor.state.doc.resolve(pos);
35846
+ for (let depth = $pos.depth; depth > 0; depth -= 1) {
35847
+ const node = $pos.node(depth);
35848
+ if (node.type.name === "table") {
35849
+ return {
35850
+ node,
35851
+ pos: $pos.before(depth),
35852
+ start: $pos.start(depth)
35853
+ };
35854
+ }
35855
+ }
35856
+ return null;
35857
+ }
35858
+ function getCellRelativePosFromDomPos(map, tableStart, domPos) {
35859
+ const relativeDomPos = domPos - tableStart;
35860
+ const seen = /* @__PURE__ */ new Set();
35861
+ for (const relativeCellPos of map.map) {
35862
+ if (seen.has(relativeCellPos)) continue;
35863
+ seen.add(relativeCellPos);
35864
+ if (relativeDomPos === relativeCellPos || relativeDomPos === relativeCellPos + 1) {
35865
+ return relativeCellPos;
35866
+ }
35867
+ }
35868
+ return null;
35869
+ }
35870
+ function buildLogicalColumnMetrics({
35871
+ editor,
35872
+ surface,
35873
+ surfaceRect,
35874
+ tableElement,
35875
+ tableInfo,
35876
+ tableLeft,
35877
+ tableWidth
35878
+ }) {
35879
+ const map = TableMap3.get(tableInfo.node);
35880
+ const fallbackWidth = metricOrFallback(tableWidth / map.width, FALLBACK_TABLE_COLUMN_WIDTH);
35881
+ const firstRow = tableElement.rows.item(0);
35882
+ const visualColumns = [];
35883
+ if (firstRow) {
35884
+ for (const tableCell of Array.from(firstRow.cells)) {
35885
+ if (!(tableCell instanceof HTMLTableCellElement)) continue;
35886
+ const cellPos = editor.view.posAtDOM(tableCell, 0);
35887
+ const relativeCellPos = getCellRelativePosFromDomPos(map, tableInfo.start, cellPos);
35888
+ if (relativeCellPos == null) continue;
35889
+ const cellMapRect = map.findCell(relativeCellPos);
35890
+ const cellRect = tableCell.getBoundingClientRect();
35891
+ const cellStart = cellRect.width > 0 ? cellRect.left - surfaceRect.left + surface.scrollLeft : tableLeft + cellMapRect.left * fallbackWidth;
35892
+ const size = metricOrFallback(cellRect.width, fallbackWidth * Math.max(1, cellMapRect.right - cellMapRect.left));
35893
+ visualColumns.push({
35894
+ index: cellMapRect.left,
35895
+ cellPos: tableInfo.start + relativeCellPos,
35896
+ start: cellStart,
35897
+ size,
35898
+ center: cellStart + size / 2
35899
+ });
35900
+ }
35901
+ }
35902
+ if (visualColumns.length > 0) {
35903
+ return visualColumns.sort((a, b) => a.index - b.index);
35904
+ }
35905
+ const cols = Array.from(tableElement.querySelectorAll("colgroup > col"));
35906
+ const parsedWidths = cols.slice(0, map.width).map((col) => parsePixelMetric(col.style.width) ?? parsePixelMetric(col.getAttribute("width")));
35907
+ const hasCompleteColWidths = parsedWidths.length >= map.width && parsedWidths.every((width) => typeof width === "number");
35908
+ let cursor = tableLeft;
35909
+ return Array.from({ length: map.width }, (_, index) => {
35910
+ const size = hasCompleteColWidths ? parsedWidths[index] : fallbackWidth;
35911
+ const start = hasCompleteColWidths ? cursor : tableLeft + index * fallbackWidth;
35912
+ cursor += size;
35913
+ return {
35914
+ index,
35915
+ cellPos: tableInfo.start + map.positionAt(0, index, tableInfo.node),
35916
+ start,
35917
+ size,
35918
+ center: start + size / 2
35919
+ };
35920
+ });
35921
+ }
35922
+ function buildLogicalRowMetrics({
35923
+ editor,
35924
+ surface,
35925
+ surfaceRect,
35926
+ tableInfo,
35927
+ rows,
35928
+ tableTop,
35929
+ tableHeight,
35930
+ cornerCell
35931
+ }) {
35932
+ const map = TableMap3.get(tableInfo.node);
35933
+ const fallbackHeight = metricOrFallback(tableHeight / map.height, FALLBACK_TABLE_ROW_HEIGHT);
35934
+ const visualRows = [];
35935
+ const seenCellPositions = /* @__PURE__ */ new Set();
35936
+ for (let rowIndex = 0; rowIndex < map.height; rowIndex += 1) {
35937
+ const relativeCellPos = map.map[rowIndex * map.width];
35938
+ if (seenCellPositions.has(relativeCellPos)) continue;
35939
+ seenCellPositions.add(relativeCellPos);
35940
+ const cellMapRect = map.findCell(relativeCellPos);
35941
+ const cellDom = editor.view.nodeDOM(tableInfo.start + relativeCellPos);
35942
+ const tableCell = cellDom instanceof HTMLTableCellElement ? cellDom : null;
35943
+ if (tableCell) {
35944
+ const cellRect = tableCell.getBoundingClientRect();
35945
+ const start = cellRect.height > 0 ? cellRect.top - surfaceRect.top + surface.scrollTop : tableTop + cellMapRect.top * fallbackHeight;
35946
+ const size = metricOrFallback(cellRect.height, fallbackHeight * Math.max(1, cellMapRect.bottom - cellMapRect.top));
35947
+ visualRows.push({
35948
+ index: cellMapRect.top,
35949
+ cellPos: tableInfo.start + relativeCellPos,
35950
+ start,
35951
+ size,
35952
+ center: start + size / 2
35953
+ });
35954
+ }
35955
+ }
35956
+ if (visualRows.length > 0) {
35957
+ return visualRows.sort((a, b) => a.index - b.index);
35958
+ }
35959
+ return rows.map((tableRow, index) => {
35960
+ const rowRect = tableRow.getBoundingClientRect();
35961
+ const anchorCell = tableRow.cells.item(0) ?? cornerCell;
35962
+ const start = rowRect.height > 0 ? rowRect.top - surfaceRect.top + surface.scrollTop : tableTop + index * fallbackHeight;
35963
+ const size = metricOrFallback(rowRect.height, fallbackHeight);
35964
+ return {
35965
+ index,
35966
+ cellPos: editor.view.posAtDOM(anchorCell, 0),
35967
+ start,
35968
+ size,
35969
+ center: start + size / 2
35970
+ };
35971
+ });
35972
+ }
35973
+ function buildTableControlLayout(editor, surface, cell) {
35974
+ const row = cell.closest("tr");
35975
+ const table = cell.closest("table");
35976
+ if (!(row instanceof HTMLTableRowElement) || !(table instanceof HTMLTableElement)) {
35977
+ return null;
35978
+ }
35979
+ const rows = Array.from(table.rows).filter((item) => item instanceof HTMLTableRowElement);
35980
+ const cornerCell = getLastCell(table);
35981
+ const cellPos = editor.view.posAtDOM(cell, 0);
35982
+ const tableInfo = findTableInfo(editor, cellPos);
35983
+ if (rows.length === 0 || !tableInfo || !(cornerCell instanceof HTMLTableCellElement)) {
35984
+ return null;
35985
+ }
35986
+ const map = TableMap3.get(tableInfo.node);
35987
+ const surfaceRect = surface.getBoundingClientRect();
35988
+ const tableRect = table.getBoundingClientRect();
35989
+ const wrapperElement = table.closest(".tableWrapper");
35990
+ const wrapper = wrapperElement instanceof HTMLElement ? wrapperElement : null;
35991
+ const wrapperRect = wrapper?.getBoundingClientRect() ?? tableRect;
35992
+ const tableLeft = tableRect.left - surfaceRect.left + surface.scrollLeft;
35993
+ const tableTop = tableRect.top - surfaceRect.top + surface.scrollTop;
35994
+ const avgRowHeight = metricOrFallback(tableRect.height / rows.length, FALLBACK_TABLE_ROW_HEIGHT);
35995
+ const avgColumnWidth = metricOrFallback(tableRect.width / map.width, FALLBACK_TABLE_COLUMN_WIDTH);
35996
+ const tableWidth = metricOrFallback(tableRect.width, avgColumnWidth * map.width);
35997
+ const tableHeight = metricOrFallback(tableRect.height, avgRowHeight * rows.length);
35998
+ const wrapperLeft = wrapperRect.left - surfaceRect.left + surface.scrollLeft;
35999
+ const wrapperTop = wrapperRect.top - surfaceRect.top + surface.scrollTop;
36000
+ const wrapperWidth = metricOrFallback(wrapperRect.width, tableWidth);
36001
+ const wrapperHeight = metricOrFallback(wrapperRect.height, tableHeight);
36002
+ const viewportWidth = metricOrFallback(wrapper?.clientWidth ?? wrapperRect.width, tableWidth);
36003
+ const viewportHeight = metricOrFallback(wrapper?.clientHeight ?? wrapperRect.height, tableHeight);
36004
+ const verticalScrollbarWidth = Math.max(0, Math.round(wrapperWidth - viewportWidth));
36005
+ const horizontalScrollbarHeight = Math.max(0, Math.round(wrapperHeight - viewportHeight));
36006
+ const rowHandles = buildLogicalRowMetrics({
36007
+ editor,
36008
+ surface,
36009
+ surfaceRect,
36010
+ tableInfo,
36011
+ rows,
36012
+ tableTop,
36013
+ tableHeight,
36014
+ cornerCell
36015
+ });
36016
+ const columnHandles = buildLogicalColumnMetrics({
36017
+ editor,
36018
+ surface,
36019
+ surfaceRect,
36020
+ tableElement: table,
36021
+ tableInfo,
36022
+ tableLeft,
36023
+ tableWidth
36024
+ });
36025
+ const activeCellRelativePos = getCellRelativePosFromDomPos(map, tableInfo.start, cellPos);
36026
+ const activeCellRect = activeCellRelativePos != null ? map.findCell(activeCellRelativePos) : { left: cell.cellIndex, top: row.rowIndex };
36027
+ const normalizedCellPos = activeCellRelativePos != null ? tableInfo.start + activeCellRelativePos : cellPos;
36028
+ return {
36029
+ cellPos: normalizedCellPos,
36030
+ cornerCellPos: tableInfo.start + map.positionAt(map.height - 1, map.width - 1, tableInfo.node),
36031
+ activeRowIndex: activeCellRect.top,
36032
+ activeColumnIndex: activeCellRect.left,
36033
+ tableLeft,
36034
+ tableTop,
36035
+ tableWidth,
36036
+ tableHeight,
36037
+ wrapperLeft,
36038
+ wrapperTop,
36039
+ wrapperWidth,
36040
+ wrapperHeight,
36041
+ viewportWidth,
36042
+ viewportHeight,
36043
+ horizontalScrollbarHeight,
36044
+ verticalScrollbarWidth,
36045
+ avgRowHeight,
36046
+ avgColumnWidth,
36047
+ rowHandles,
36048
+ columnHandles
36049
+ };
36050
+ }
36051
+
34947
36052
  // src/components/UEditor/table-hover-state.ts
34948
36053
  var MENU_HOVER_PADDING = 18;
34949
36054
  var ROW_HANDLE_HOVER_WIDTH = 28;
@@ -34977,18 +36082,18 @@ function buildTableHoverState({
34977
36082
  const directAddRow = targetElement?.closest?.("[data-table-control='add-row']");
34978
36083
  const directRowHandleIndex = directRowHandle instanceof HTMLElement ? Number.parseInt(directRowHandle.dataset.rowHandleIndex ?? "", 10) : Number.NaN;
34979
36084
  const directColumnHandleIndex = directColumnHandle instanceof HTMLElement ? Number.parseInt(directColumnHandle.dataset.columnHandleIndex ?? "", 10) : Number.NaN;
34980
- const visibleTableWidth = Math.min(layout.tableWidth, layout.viewportWidth);
34981
- const visibleTableHeight = Math.min(layout.tableHeight, layout.viewportHeight);
36085
+ const visibleBounds = getVisibleTableBounds(layout);
36086
+ const rowRailTop = layout.wrapperTop + layout.wrapperHeight;
34982
36087
  const isMouseInTable = relativeX >= layout.tableLeft && relativeX <= layout.tableLeft + layout.tableWidth && relativeY >= layout.tableTop && relativeY <= layout.tableTop + layout.tableHeight;
34983
36088
  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;
34984
36089
  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;
34985
36090
  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;
34986
36091
  const lastRow = layout.rowHandles[layout.rowHandles.length - 1];
34987
36092
  const lastCol = layout.columnHandles[layout.columnHandles.length - 1];
34988
- const isMouseInLastColumn = lastCol ? relativeX >= lastCol.start && relativeX <= lastCol.start + lastCol.size && relativeY >= layout.tableTop && relativeY <= layout.tableTop + visibleTableHeight : false;
34989
- const addColumnVisible = Boolean(directAddColumn) || relativeX >= layout.tableLeft + visibleTableWidth && relativeX <= layout.tableLeft + visibleTableWidth + ADD_COLUMN_HOVER_WIDTH && relativeY >= layout.tableTop && relativeY <= layout.tableTop + visibleTableHeight || isMouseInLastColumn;
34990
- const isMouseInLastRow = lastRow ? relativeY >= lastRow.start && relativeY <= lastRow.start + lastRow.size && relativeX >= layout.tableLeft && relativeX <= layout.tableLeft + visibleTableWidth : false;
34991
- const addRowVisible = Boolean(directAddRow) || relativeY >= layout.tableTop + visibleTableHeight && relativeY <= layout.tableTop + visibleTableHeight + ADD_ROW_HOVER_HEIGHT && relativeX >= layout.tableLeft && relativeX <= layout.tableLeft + visibleTableWidth || isMouseInLastRow;
36093
+ const isMouseInLastColumn = lastCol ? relativeX >= lastCol.start && relativeX <= lastCol.start + lastCol.size && relativeY >= visibleBounds.top && relativeY <= visibleBounds.bottom : false;
36094
+ const addColumnVisible = Boolean(directAddColumn) || relativeX >= visibleBounds.right && relativeX <= visibleBounds.right + ADD_COLUMN_HOVER_WIDTH && relativeY >= visibleBounds.top && relativeY <= visibleBounds.bottom || isMouseInLastColumn;
36095
+ const isMouseInLastRow = lastRow ? relativeY >= lastRow.start && relativeY <= lastRow.start + lastRow.size && relativeX >= visibleBounds.left && relativeX <= visibleBounds.right : false;
36096
+ const addRowVisible = Boolean(directAddRow) || relativeY >= rowRailTop && relativeY <= rowRailTop + ADD_ROW_HOVER_HEIGHT && relativeX >= visibleBounds.left && relativeX <= visibleBounds.right || isMouseInLastRow;
34992
36097
  return {
34993
36098
  menuVisible,
34994
36099
  addColumnVisible,
@@ -35110,12 +36215,11 @@ function TableAddRails({
35110
36215
  quickAddColumnLabel,
35111
36216
  quickAddRowLabel
35112
36217
  }) {
35113
- const visibleTableWidth = Math.min(layout.tableWidth, layout.viewportWidth);
35114
- const visibleTableHeight = Math.min(layout.tableHeight, layout.viewportHeight);
35115
- const columnRailTop = layout.tableTop;
35116
- const columnRailLeft = layout.tableLeft + visibleTableWidth + ADD_COLUMN_RAIL_GAP;
36218
+ const visibleBounds = getVisibleTableBounds(layout);
36219
+ const columnRailTop = visibleBounds.top;
36220
+ const columnRailLeft = visibleBounds.right + ADD_COLUMN_RAIL_GAP;
35117
36221
  const rowRailTop = layout.wrapperTop + layout.wrapperHeight + ADD_ROW_RAIL_GAP;
35118
- const rowRailLeft = Math.max(layout.tableLeft, layout.wrapperLeft);
36222
+ const rowRailLeft = visibleBounds.left;
35119
36223
  const showColumnRail = controlsVisible || addColumnVisible;
35120
36224
  const showRowRail = controlsVisible || addRowVisible;
35121
36225
  return /* @__PURE__ */ jsxs75(Fragment32, { children: [
@@ -35143,10 +36247,10 @@ function TableAddRails({
35143
36247
  "transition-[opacity,transform,colors] duration-150 hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed"
35144
36248
  ),
35145
36249
  style: {
35146
- top: showColumnRail ? columnRailTop : columnRailTop + Math.max(0, visibleTableHeight / 2 - 24),
36250
+ top: showColumnRail ? columnRailTop : columnRailTop + Math.max(0, visibleBounds.height / 2 - 24),
35147
36251
  left: columnRailLeft,
35148
36252
  width: showColumnRail ? 18 : 12,
35149
- height: showColumnRail ? visibleTableHeight : 48,
36253
+ height: showColumnRail ? visibleBounds.height : 48,
35150
36254
  opacity: showColumnRail ? 1 : 0,
35151
36255
  transform: showColumnRail ? "scale(1)" : "scale(0.92)",
35152
36256
  pointerEvents: showColumnRail ? "auto" : "none"
@@ -35181,8 +36285,8 @@ function TableAddRails({
35181
36285
  ),
35182
36286
  style: {
35183
36287
  top: rowRailTop,
35184
- left: showRowRail ? rowRailLeft : rowRailLeft + Math.max(0, visibleTableWidth / 2 - 24),
35185
- width: showRowRail ? visibleTableWidth : 48,
36288
+ left: showRowRail ? rowRailLeft : rowRailLeft + Math.max(0, visibleBounds.width / 2 - 24),
36289
+ width: showRowRail ? visibleBounds.width : 48,
35186
36290
  height: showRowRail ? 16 : 12,
35187
36291
  opacity: showRowRail ? 1 : 0,
35188
36292
  transform: showRowRail ? "scale(1)" : "scale(0.92)",
@@ -35407,248 +36511,6 @@ function TableColumnHandles({
35407
36511
  }) });
35408
36512
  }
35409
36513
 
35410
- // src/components/UEditor/table-layout-model.ts
35411
- var FALLBACK_TABLE_ROW_HEIGHT = 44;
35412
- var FALLBACK_TABLE_COLUMN_WIDTH = 160;
35413
- function metricOrFallback(value, fallback) {
35414
- return Number.isFinite(value) && value > 0 ? value : fallback;
35415
- }
35416
- function parsePixelMetric(value) {
35417
- if (!value) return null;
35418
- const parsed = Number.parseFloat(value);
35419
- return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
35420
- }
35421
- function getPrimaryCell(table) {
35422
- const cell = table.querySelector("th,td");
35423
- return cell instanceof HTMLTableCellElement ? cell : null;
35424
- }
35425
- function getLastCell(table) {
35426
- const lastRow = table.rows.item(table.rows.length - 1);
35427
- if (!(lastRow instanceof HTMLTableRowElement)) return null;
35428
- const cell = lastRow.cells.item(lastRow.cells.length - 1);
35429
- return cell instanceof HTMLTableCellElement ? cell : null;
35430
- }
35431
- function getCellFromTarget(target) {
35432
- const element = resolveEventElement(target);
35433
- if (!element) return null;
35434
- const directCell = element.closest("th,td");
35435
- if (directCell instanceof HTMLTableCellElement) {
35436
- return directCell;
35437
- }
35438
- const table = element.closest("table");
35439
- if (table instanceof HTMLTableElement) {
35440
- return getPrimaryCell(table);
35441
- }
35442
- return null;
35443
- }
35444
- function findTableInfo(editor, pos) {
35445
- const $pos = editor.state.doc.resolve(pos);
35446
- for (let depth = $pos.depth; depth > 0; depth -= 1) {
35447
- const node = $pos.node(depth);
35448
- if (node.type.name === "table") {
35449
- return {
35450
- node,
35451
- pos: $pos.before(depth),
35452
- start: $pos.start(depth)
35453
- };
35454
- }
35455
- }
35456
- return null;
35457
- }
35458
- function getCellRelativePosFromDomPos(map, tableStart, domPos) {
35459
- const relativeDomPos = domPos - tableStart;
35460
- const seen = /* @__PURE__ */ new Set();
35461
- for (const relativeCellPos of map.map) {
35462
- if (seen.has(relativeCellPos)) continue;
35463
- seen.add(relativeCellPos);
35464
- if (relativeDomPos === relativeCellPos || relativeDomPos === relativeCellPos + 1) {
35465
- return relativeCellPos;
35466
- }
35467
- }
35468
- return null;
35469
- }
35470
- function buildLogicalColumnMetrics({
35471
- editor,
35472
- surface,
35473
- surfaceRect,
35474
- tableElement,
35475
- tableInfo,
35476
- tableLeft,
35477
- tableWidth
35478
- }) {
35479
- const map = TableMap2.get(tableInfo.node);
35480
- const fallbackWidth = metricOrFallback(tableWidth / map.width, FALLBACK_TABLE_COLUMN_WIDTH);
35481
- const firstRow = tableElement.rows.item(0);
35482
- const visualColumns = [];
35483
- if (firstRow) {
35484
- for (const tableCell of Array.from(firstRow.cells)) {
35485
- if (!(tableCell instanceof HTMLTableCellElement)) continue;
35486
- const cellPos = editor.view.posAtDOM(tableCell, 0);
35487
- const relativeCellPos = getCellRelativePosFromDomPos(map, tableInfo.start, cellPos);
35488
- if (relativeCellPos == null) continue;
35489
- const cellMapRect = map.findCell(relativeCellPos);
35490
- const cellRect = tableCell.getBoundingClientRect();
35491
- const cellStart = cellRect.width > 0 ? cellRect.left - surfaceRect.left + surface.scrollLeft : tableLeft + cellMapRect.left * fallbackWidth;
35492
- const size = metricOrFallback(cellRect.width, fallbackWidth * Math.max(1, cellMapRect.right - cellMapRect.left));
35493
- visualColumns.push({
35494
- index: cellMapRect.left,
35495
- cellPos: tableInfo.start + relativeCellPos,
35496
- start: cellStart,
35497
- size,
35498
- center: cellStart + size / 2
35499
- });
35500
- }
35501
- }
35502
- if (visualColumns.length > 0) {
35503
- return visualColumns.sort((a, b) => a.index - b.index);
35504
- }
35505
- const cols = Array.from(tableElement.querySelectorAll("colgroup > col"));
35506
- const parsedWidths = cols.slice(0, map.width).map((col) => parsePixelMetric(col.style.width) ?? parsePixelMetric(col.getAttribute("width")));
35507
- const hasCompleteColWidths = parsedWidths.length >= map.width && parsedWidths.every((width) => typeof width === "number");
35508
- let cursor = tableLeft;
35509
- return Array.from({ length: map.width }, (_, index) => {
35510
- const size = hasCompleteColWidths ? parsedWidths[index] : fallbackWidth;
35511
- const start = hasCompleteColWidths ? cursor : tableLeft + index * fallbackWidth;
35512
- cursor += size;
35513
- return {
35514
- index,
35515
- cellPos: tableInfo.start + map.positionAt(0, index, tableInfo.node),
35516
- start,
35517
- size,
35518
- center: start + size / 2
35519
- };
35520
- });
35521
- }
35522
- function buildLogicalRowMetrics({
35523
- editor,
35524
- surface,
35525
- surfaceRect,
35526
- tableInfo,
35527
- rows,
35528
- tableTop,
35529
- tableHeight,
35530
- cornerCell
35531
- }) {
35532
- const map = TableMap2.get(tableInfo.node);
35533
- const fallbackHeight = metricOrFallback(tableHeight / map.height, FALLBACK_TABLE_ROW_HEIGHT);
35534
- const visualRows = [];
35535
- const seenCellPositions = /* @__PURE__ */ new Set();
35536
- for (let rowIndex = 0; rowIndex < map.height; rowIndex += 1) {
35537
- const relativeCellPos = map.map[rowIndex * map.width];
35538
- if (seenCellPositions.has(relativeCellPos)) continue;
35539
- seenCellPositions.add(relativeCellPos);
35540
- const cellMapRect = map.findCell(relativeCellPos);
35541
- const cellDom = editor.view.nodeDOM(tableInfo.start + relativeCellPos);
35542
- const tableCell = cellDom instanceof HTMLTableCellElement ? cellDom : null;
35543
- if (tableCell) {
35544
- const cellRect = tableCell.getBoundingClientRect();
35545
- const start = cellRect.height > 0 ? cellRect.top - surfaceRect.top + surface.scrollTop : tableTop + cellMapRect.top * fallbackHeight;
35546
- const size = metricOrFallback(cellRect.height, fallbackHeight * Math.max(1, cellMapRect.bottom - cellMapRect.top));
35547
- visualRows.push({
35548
- index: cellMapRect.top,
35549
- cellPos: tableInfo.start + relativeCellPos,
35550
- start,
35551
- size,
35552
- center: start + size / 2
35553
- });
35554
- }
35555
- }
35556
- if (visualRows.length > 0) {
35557
- return visualRows.sort((a, b) => a.index - b.index);
35558
- }
35559
- return rows.map((tableRow, index) => {
35560
- const rowRect = tableRow.getBoundingClientRect();
35561
- const anchorCell = tableRow.cells.item(0) ?? cornerCell;
35562
- const start = rowRect.height > 0 ? rowRect.top - surfaceRect.top + surface.scrollTop : tableTop + index * fallbackHeight;
35563
- const size = metricOrFallback(rowRect.height, fallbackHeight);
35564
- return {
35565
- index,
35566
- cellPos: editor.view.posAtDOM(anchorCell, 0),
35567
- start,
35568
- size,
35569
- center: start + size / 2
35570
- };
35571
- });
35572
- }
35573
- function buildTableControlLayout(editor, surface, cell) {
35574
- const row = cell.closest("tr");
35575
- const table = cell.closest("table");
35576
- if (!(row instanceof HTMLTableRowElement) || !(table instanceof HTMLTableElement)) {
35577
- return null;
35578
- }
35579
- const rows = Array.from(table.rows).filter((item) => item instanceof HTMLTableRowElement);
35580
- const cornerCell = getLastCell(table);
35581
- const cellPos = editor.view.posAtDOM(cell, 0);
35582
- const tableInfo = findTableInfo(editor, cellPos);
35583
- if (rows.length === 0 || !tableInfo || !(cornerCell instanceof HTMLTableCellElement)) {
35584
- return null;
35585
- }
35586
- const map = TableMap2.get(tableInfo.node);
35587
- const surfaceRect = surface.getBoundingClientRect();
35588
- const tableRect = table.getBoundingClientRect();
35589
- const wrapperElement = table.closest(".tableWrapper");
35590
- const wrapper = wrapperElement instanceof HTMLElement ? wrapperElement : null;
35591
- const wrapperRect = wrapper?.getBoundingClientRect() ?? tableRect;
35592
- const tableLeft = tableRect.left - surfaceRect.left + surface.scrollLeft;
35593
- const tableTop = tableRect.top - surfaceRect.top + surface.scrollTop;
35594
- const avgRowHeight = metricOrFallback(tableRect.height / rows.length, FALLBACK_TABLE_ROW_HEIGHT);
35595
- const avgColumnWidth = metricOrFallback(tableRect.width / map.width, FALLBACK_TABLE_COLUMN_WIDTH);
35596
- const tableWidth = metricOrFallback(tableRect.width, avgColumnWidth * map.width);
35597
- const tableHeight = metricOrFallback(tableRect.height, avgRowHeight * rows.length);
35598
- const wrapperLeft = wrapperRect.left - surfaceRect.left + surface.scrollLeft;
35599
- const wrapperTop = wrapperRect.top - surfaceRect.top + surface.scrollTop;
35600
- const wrapperWidth = metricOrFallback(wrapperRect.width, tableWidth);
35601
- const wrapperHeight = metricOrFallback(wrapperRect.height, tableHeight);
35602
- const viewportWidth = metricOrFallback(wrapper?.clientWidth ?? wrapperRect.width, tableWidth);
35603
- const viewportHeight = metricOrFallback(wrapper?.clientHeight ?? wrapperRect.height, tableHeight);
35604
- const verticalScrollbarWidth = Math.max(0, Math.round(wrapperWidth - viewportWidth));
35605
- const horizontalScrollbarHeight = Math.max(0, Math.round(wrapperHeight - viewportHeight));
35606
- const rowHandles = buildLogicalRowMetrics({
35607
- editor,
35608
- surface,
35609
- surfaceRect,
35610
- tableInfo,
35611
- rows,
35612
- tableTop,
35613
- tableHeight,
35614
- cornerCell
35615
- });
35616
- const columnHandles = buildLogicalColumnMetrics({
35617
- editor,
35618
- surface,
35619
- surfaceRect,
35620
- tableElement: table,
35621
- tableInfo,
35622
- tableLeft,
35623
- tableWidth
35624
- });
35625
- const activeCellRelativePos = getCellRelativePosFromDomPos(map, tableInfo.start, cellPos);
35626
- const activeCellRect = activeCellRelativePos != null ? map.findCell(activeCellRelativePos) : { left: cell.cellIndex, top: row.rowIndex };
35627
- const normalizedCellPos = activeCellRelativePos != null ? tableInfo.start + activeCellRelativePos : cellPos;
35628
- return {
35629
- cellPos: normalizedCellPos,
35630
- cornerCellPos: tableInfo.start + map.positionAt(map.height - 1, map.width - 1, tableInfo.node),
35631
- activeRowIndex: activeCellRect.top,
35632
- activeColumnIndex: activeCellRect.left,
35633
- tableLeft,
35634
- tableTop,
35635
- tableWidth,
35636
- tableHeight,
35637
- wrapperLeft,
35638
- wrapperTop,
35639
- wrapperWidth,
35640
- wrapperHeight,
35641
- viewportWidth,
35642
- viewportHeight,
35643
- horizontalScrollbarHeight,
35644
- verticalScrollbarWidth,
35645
- avgRowHeight,
35646
- avgColumnWidth,
35647
- rowHandles,
35648
- columnHandles
35649
- };
35650
- }
35651
-
35652
36514
  // src/components/UEditor/table-controls.tsx
35653
36515
  import { Fragment as Fragment34, jsx as jsx91, jsxs as jsxs76 } from "react/jsx-runtime";
35654
36516
  var TABLE_MENU_TOP_OFFSET = 10;
@@ -37623,6 +38485,7 @@ var UEditor = React82.forwardRef(({
37623
38485
  const effectivePlaceholder = placeholder ?? t("placeholder");
37624
38486
  const inFlightPrepareRef = useRef37(null);
37625
38487
  const lastAppliedContentRef = useRef37(content ?? "");
38488
+ const scheduledFormulaRecalculateRef = useRef37(false);
37626
38489
  const resolvedUploadFile = useMemo24(() => {
37627
38490
  if (uploadFile) return uploadFile;
37628
38491
  if (uploadFileForSave) {
@@ -37686,7 +38549,16 @@ var UEditor = React82.forwardRef(({
37686
38549
  class: UEDITOR_PROSEMIRROR_CLASS_NAME
37687
38550
  }
37688
38551
  },
37689
- onUpdate: ({ editor: editor2 }) => {
38552
+ onUpdate: ({ editor: editor2, transaction }) => {
38553
+ if (!transaction.getMeta(UEDITOR_TABLE_FORMULA_RECALCULATE_META) && !scheduledFormulaRecalculateRef.current) {
38554
+ scheduledFormulaRecalculateRef.current = true;
38555
+ queueMicrotask(() => {
38556
+ scheduledFormulaRecalculateRef.current = false;
38557
+ if (!editor2.isDestroyed) {
38558
+ recalculateAllTableFormulas(editor2);
38559
+ }
38560
+ });
38561
+ }
37690
38562
  const html = editor2.getHTML();
37691
38563
  onChange?.(html);
37692
38564
  onHtmlChange?.(html);
@@ -37724,6 +38596,14 @@ var UEditor = React82.forwardRef(({
37724
38596
  }),
37725
38597
  [content, editor, uploadImageForSave, uploadFileForSave, uploadImageConcurrency]
37726
38598
  );
38599
+ useEffect38(() => {
38600
+ if (!editor) return;
38601
+ queueMicrotask(() => {
38602
+ if (!editor.isDestroyed) {
38603
+ recalculateAllTableFormulas(editor);
38604
+ }
38605
+ });
38606
+ }, [editor]);
37727
38607
  useEffect38(() => {
37728
38608
  if (!editor) return;
37729
38609
  const nextContent = content ?? "";
@@ -37733,6 +38613,11 @@ var UEditor = React82.forwardRef(({
37733
38613
  Promise.resolve().then(() => {
37734
38614
  if (!editor.isDestroyed) {
37735
38615
  editor.commands.setContent(nextContent, { emitUpdate: false });
38616
+ queueMicrotask(() => {
38617
+ if (!editor.isDestroyed) {
38618
+ recalculateAllTableFormulas(editor);
38619
+ }
38620
+ });
37736
38621
  }
37737
38622
  });
37738
38623
  }