@underverse-ui/underverse 1.0.149 → 1.0.151

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: {
@@ -25681,6 +25689,8 @@ import { Extension as Extension2 } from "@tiptap/core";
25681
25689
  import { Plugin as Plugin2 } from "@tiptap/pm/state";
25682
25690
 
25683
25691
  // src/components/UEditor/clipboard-tables.ts
25692
+ var DEFAULT_HTML_TABLE_CELL_BACKGROUND_COLOR = "#ffffff";
25693
+ var DEFAULT_HTML_TABLE_TEXT_COLOR = "#000000";
25684
25694
  function getClipboardData(dataTransfer, type) {
25685
25695
  try {
25686
25696
  return dataTransfer.getData(type) ?? "";
@@ -25701,6 +25711,411 @@ function extractClipboardHtmlFragment(html) {
25701
25711
  function normalizeClipboardCellText(value) {
25702
25712
  return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\u00a0/g, " ").replace(/[ \t]+\n/g, "\n").replace(/\n[ \t]+/g, "\n").replace(/\n+$/g, "").replace(/^\n+/g, "").trim();
25703
25713
  }
25714
+ function parseStyleDeclarations(styleText) {
25715
+ const declarations = /* @__PURE__ */ new Map();
25716
+ if (!styleText) return declarations;
25717
+ for (const declaration of styleText.split(";")) {
25718
+ const separatorIndex = declaration.indexOf(":");
25719
+ if (separatorIndex <= 0) continue;
25720
+ const property = declaration.slice(0, separatorIndex).trim().toLowerCase();
25721
+ const value = cleanStyleValue(declaration.slice(separatorIndex + 1));
25722
+ if (!property || !value) continue;
25723
+ declarations.set(property, value);
25724
+ }
25725
+ return declarations;
25726
+ }
25727
+ function mergeStyleDeclarations(...sources) {
25728
+ const declarations = /* @__PURE__ */ new Map();
25729
+ for (const source of sources) {
25730
+ if (!source) continue;
25731
+ for (const [property, value] of source.entries()) {
25732
+ declarations.set(property, value);
25733
+ }
25734
+ }
25735
+ return declarations;
25736
+ }
25737
+ function extractCssClassNames(selectorText) {
25738
+ const classNames = /* @__PURE__ */ new Set();
25739
+ const classNamePattern = /\.([_a-zA-Z-][\w-]*)/g;
25740
+ let match;
25741
+ while ((match = classNamePattern.exec(selectorText)) !== null) {
25742
+ classNames.add(match[1]);
25743
+ }
25744
+ return classNames;
25745
+ }
25746
+ function parseClipboardCssClassStyles(doc) {
25747
+ const styleMap = /* @__PURE__ */ new Map();
25748
+ for (const styleElement of Array.from(doc.querySelectorAll("style"))) {
25749
+ const cssText = (styleElement.textContent ?? "").replace(/<!--|-->/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
25750
+ const rulePattern = /([^{}]+)\{([^{}]*)\}/g;
25751
+ let match;
25752
+ while ((match = rulePattern.exec(cssText)) !== null) {
25753
+ const classNames = extractCssClassNames(match[1]);
25754
+ if (classNames.size === 0) continue;
25755
+ const declarations = parseStyleDeclarations(match[2]);
25756
+ if (declarations.size === 0) continue;
25757
+ for (const className of classNames) {
25758
+ styleMap.set(className, mergeStyleDeclarations(styleMap.get(className), declarations));
25759
+ }
25760
+ }
25761
+ }
25762
+ return styleMap;
25763
+ }
25764
+ function getElementStyleDeclarations(element, styleMap) {
25765
+ const classDeclarations = Array.from(element.classList).map((className) => styleMap.get(className));
25766
+ const inlineDeclarations = parseStyleDeclarations(element.getAttribute("style"));
25767
+ return mergeStyleDeclarations(...classDeclarations, inlineDeclarations);
25768
+ }
25769
+ function cleanStyleValue(value) {
25770
+ const normalized = value?.trim();
25771
+ if (!normalized) return null;
25772
+ if (/[\0<>;{}]/.test(normalized)) return null;
25773
+ if (/\b(?:expression|url|(?:repeating-)?(?:linear|radial|conic)-gradient)\s*\(/i.test(normalized)) return null;
25774
+ return normalized;
25775
+ }
25776
+ function normalizeColorValue(value) {
25777
+ const normalized = cleanStyleValue(value);
25778
+ if (!normalized) return null;
25779
+ if (/^(?:auto|inherit|initial|none|transparent|unset)$/i.test(normalized)) return null;
25780
+ return normalized;
25781
+ }
25782
+ function normalizeTextColorValue(value) {
25783
+ const normalized = normalizeColorValue(value);
25784
+ if (!normalized) return null;
25785
+ if (/^(?:automatic|windowtext|black|#000|#000000|rgb\(\s*0\s*,\s*0\s*,\s*0\s*\))$/i.test(normalized)) {
25786
+ return DEFAULT_HTML_TABLE_TEXT_COLOR;
25787
+ }
25788
+ return normalized;
25789
+ }
25790
+ function isWhiteColor(value) {
25791
+ if (!value) return false;
25792
+ return /^(?:white|#fff|#ffffff|rgb\(\s*255\s*,\s*255\s*,\s*255\s*\))$/i.test(value.trim());
25793
+ }
25794
+ function parseCssColorRgb(value) {
25795
+ const normalized = normalizeColorValue(value);
25796
+ if (!normalized) return null;
25797
+ const lowerColor = normalized.toLowerCase();
25798
+ if (lowerColor === "white") return { r: 255, g: 255, b: 255 };
25799
+ if (lowerColor === "black") return { r: 0, g: 0, b: 0 };
25800
+ const hexMatch = lowerColor.match(/^#([\da-f]{3}|[\da-f]{6})$/i);
25801
+ if (hexMatch) {
25802
+ const hex = hexMatch[1];
25803
+ const fullHex = hex.length === 3 ? hex.split("").map((part) => part + part).join("") : hex;
25804
+ return {
25805
+ r: Number.parseInt(fullHex.slice(0, 2), 16),
25806
+ g: Number.parseInt(fullHex.slice(2, 4), 16),
25807
+ b: Number.parseInt(fullHex.slice(4, 6), 16)
25808
+ };
25809
+ }
25810
+ const rgbMatch = lowerColor.match(/^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)/);
25811
+ if (rgbMatch) {
25812
+ return {
25813
+ r: Math.max(0, Math.min(255, Number.parseFloat(rgbMatch[1]))),
25814
+ g: Math.max(0, Math.min(255, Number.parseFloat(rgbMatch[2]))),
25815
+ b: Math.max(0, Math.min(255, Number.parseFloat(rgbMatch[3])))
25816
+ };
25817
+ }
25818
+ return null;
25819
+ }
25820
+ function getRelativeLuminance(value) {
25821
+ const rgb = parseCssColorRgb(value);
25822
+ if (!rgb) return null;
25823
+ const toLinear = (channel) => {
25824
+ const normalized = channel / 255;
25825
+ return normalized <= 0.03928 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4;
25826
+ };
25827
+ return 0.2126 * toLinear(rgb.r) + 0.7152 * toLinear(rgb.g) + 0.0722 * toLinear(rgb.b);
25828
+ }
25829
+ function isLightTextColor(value) {
25830
+ const luminance = getRelativeLuminance(value);
25831
+ return luminance !== null && luminance >= 0.72;
25832
+ }
25833
+ function isDarkReadableBackground(value) {
25834
+ const luminance = getRelativeLuminance(value);
25835
+ return luminance !== null && luminance <= 0.45;
25836
+ }
25837
+ function splitCssTokens(value) {
25838
+ const tokens = [];
25839
+ let current = "";
25840
+ let depth = 0;
25841
+ for (const char of value) {
25842
+ if (char === "(") depth += 1;
25843
+ if (char === ")") depth = Math.max(0, depth - 1);
25844
+ if (/\s/.test(char) && depth === 0) {
25845
+ if (current) {
25846
+ tokens.push(current);
25847
+ current = "";
25848
+ }
25849
+ continue;
25850
+ }
25851
+ current += char;
25852
+ }
25853
+ if (current) tokens.push(current);
25854
+ return tokens;
25855
+ }
25856
+ function extractColorFromShorthand(value) {
25857
+ const normalized = cleanStyleValue(value);
25858
+ if (!normalized) return null;
25859
+ const explicitColor = normalized.match(/#[\da-f]{3,8}\b|rgba?\([^)]+\)|hsla?\([^)]+\)/i);
25860
+ if (explicitColor) return explicitColor[0];
25861
+ const ignoredKeywords = /* @__PURE__ */ new Set([
25862
+ "border-box",
25863
+ "center",
25864
+ "contain",
25865
+ "content-box",
25866
+ "cover",
25867
+ "fixed",
25868
+ "inherit",
25869
+ "initial",
25870
+ "left",
25871
+ "local",
25872
+ "none",
25873
+ "no-repeat",
25874
+ "padding-box",
25875
+ "repeat",
25876
+ "repeat-x",
25877
+ "repeat-y",
25878
+ "right",
25879
+ "scroll",
25880
+ "top",
25881
+ "transparent",
25882
+ "unset"
25883
+ ]);
25884
+ return splitCssTokens(normalized).find((token) => !ignoredKeywords.has(token.toLowerCase())) ?? null;
25885
+ }
25886
+ function getBackgroundColor(styles) {
25887
+ return normalizeColorValue(styles.get("background-color")) ?? normalizeColorValue(extractColorFromShorthand(styles.get("background")));
25888
+ }
25889
+ var BORDER_STYLES = /* @__PURE__ */ new Set([
25890
+ "dashed",
25891
+ "dotted",
25892
+ "double",
25893
+ "groove",
25894
+ "hidden",
25895
+ "inset",
25896
+ "none",
25897
+ "outset",
25898
+ "ridge",
25899
+ "solid"
25900
+ ]);
25901
+ var BORDER_WIDTH_KEYWORDS = /* @__PURE__ */ new Set(["medium", "thick", "thin"]);
25902
+ function normalizeBorderStyle(value) {
25903
+ const normalized = cleanStyleValue(value);
25904
+ if (!normalized) return null;
25905
+ const styles = splitCssTokens(normalized).filter((token) => BORDER_STYLES.has(token.toLowerCase()));
25906
+ const usefulStyles = styles.filter((style) => !/^(?:hidden|none)$/i.test(style));
25907
+ return usefulStyles.length > 0 ? usefulStyles.join(" ") : null;
25908
+ }
25909
+ function normalizeBorderWidth(value) {
25910
+ const normalized = cleanStyleValue(value);
25911
+ if (!normalized) return null;
25912
+ const widths = splitCssTokens(normalized).filter((token) => {
25913
+ const lowerToken = token.toLowerCase();
25914
+ return BORDER_WIDTH_KEYWORDS.has(lowerToken) || /^\d*\.?\d+(?:px|pt|pc|in|cm|mm|em|rem)?$/i.test(token);
25915
+ });
25916
+ return widths.length > 0 ? widths.join(" ") : null;
25917
+ }
25918
+ function parseBorderShorthand(value) {
25919
+ const normalized = cleanStyleValue(value);
25920
+ if (!normalized) return null;
25921
+ const tokens = splitCssTokens(normalized);
25922
+ let borderStyle = null;
25923
+ let borderWidth = null;
25924
+ const colorTokens = [];
25925
+ for (const token of tokens) {
25926
+ const lowerToken = token.toLowerCase();
25927
+ if (!borderStyle && BORDER_STYLES.has(lowerToken)) {
25928
+ borderStyle = lowerToken;
25929
+ continue;
25930
+ }
25931
+ if (!borderWidth && (BORDER_WIDTH_KEYWORDS.has(lowerToken) || /^\d*\.?\d+(?:px|pt|pc|in|cm|mm|em|rem)?$/i.test(token))) {
25932
+ borderWidth = token;
25933
+ continue;
25934
+ }
25935
+ colorTokens.push(token);
25936
+ }
25937
+ if (borderStyle && /^(?:hidden|none)$/i.test(borderStyle)) return null;
25938
+ return {
25939
+ borderColor: normalizeColorValue(colorTokens.join(" ")),
25940
+ borderStyle,
25941
+ borderWidth
25942
+ };
25943
+ }
25944
+ function getFirstParsedBorder(styles) {
25945
+ for (const property of ["border", "border-top", "border-right", "border-bottom", "border-left"]) {
25946
+ const border = parseBorderShorthand(styles.get(property));
25947
+ if (border) return border;
25948
+ }
25949
+ return null;
25950
+ }
25951
+ function getBorderAttrs(styles) {
25952
+ const parsedBorder = getFirstParsedBorder(styles);
25953
+ return {
25954
+ borderColor: normalizeColorValue(styles.get("border-color")) ?? parsedBorder?.borderColor ?? void 0,
25955
+ borderStyle: normalizeBorderStyle(styles.get("border-style")) ?? parsedBorder?.borderStyle ?? void 0,
25956
+ borderWidth: normalizeBorderWidth(styles.get("border-width")) ?? parsedBorder?.borderWidth ?? void 0
25957
+ };
25958
+ }
25959
+ function parsePositiveInteger(value, max = 100) {
25960
+ if (!value) return null;
25961
+ const parsed = Number.parseInt(value, 10);
25962
+ if (!Number.isFinite(parsed) || parsed < 1) return null;
25963
+ return Math.min(parsed, max);
25964
+ }
25965
+ function parseCssSize(value) {
25966
+ const normalized = cleanStyleValue(value);
25967
+ if (!normalized) return null;
25968
+ const match = normalized.match(/^(\d+(?:\.\d+)?)(px|pt)?$/i);
25969
+ if (!match) return null;
25970
+ const amount = Number.parseFloat(match[1]);
25971
+ if (!Number.isFinite(amount) || amount <= 0) return null;
25972
+ return Math.round(match[2]?.toLowerCase() === "pt" ? amount * (4 / 3) : amount);
25973
+ }
25974
+ function getCellWidth(cell, styles, colspan) {
25975
+ if (colspan !== 1) return null;
25976
+ const width = parseCssSize(cell.getAttribute("data-colwidth") ?? cell.getAttribute("width") ?? styles.get("width"));
25977
+ return width ? [width] : null;
25978
+ }
25979
+ function getTableRowAttrs(row, styles) {
25980
+ const rowHeight = parseCssSize(
25981
+ row.getAttribute("data-row-height") ?? row.getAttribute("height") ?? styles.get("height")
25982
+ );
25983
+ return rowHeight ? { rowHeight } : void 0;
25984
+ }
25985
+ function getTableCellAttrs(cell, styles, defaultBackgroundColor) {
25986
+ const colspan = parsePositiveInteger(cell.getAttribute("colspan")) ?? 1;
25987
+ const rowspan = parsePositiveInteger(cell.getAttribute("rowspan")) ?? 1;
25988
+ const backgroundColor = getBackgroundColor(styles) ?? normalizeColorValue(cell.getAttribute("data-background-color")) ?? normalizeColorValue(cell.getAttribute("bgcolor")) ?? defaultBackgroundColor;
25989
+ const borderAttrs = getBorderAttrs(styles);
25990
+ const colwidth = getCellWidth(cell, styles, colspan);
25991
+ const attrs = {};
25992
+ if (backgroundColor) attrs.backgroundColor = backgroundColor;
25993
+ if (borderAttrs.borderColor) attrs.borderColor = borderAttrs.borderColor;
25994
+ if (borderAttrs.borderStyle) attrs.borderStyle = borderAttrs.borderStyle;
25995
+ if (borderAttrs.borderWidth) attrs.borderWidth = borderAttrs.borderWidth;
25996
+ if (colspan > 1) attrs.colspan = colspan;
25997
+ if (rowspan > 1) attrs.rowspan = rowspan;
25998
+ if (colwidth) attrs.colwidth = colwidth;
25999
+ return Object.keys(attrs).length > 0 ? attrs : void 0;
26000
+ }
26001
+ function marksEqual(left, right) {
26002
+ return JSON.stringify(left ?? []) === JSON.stringify(right ?? []);
26003
+ }
26004
+ function mergeMarks(base2, additions) {
26005
+ const next = [...base2 ?? []];
26006
+ for (const addition of additions ?? []) {
26007
+ const existingIndex = next.findIndex((mark) => mark.type === addition.type);
26008
+ if (existingIndex >= 0) {
26009
+ const existingMark = next[existingIndex];
26010
+ next[existingIndex] = {
26011
+ ...existingMark,
26012
+ attrs: {
26013
+ ...existingMark.attrs ?? {},
26014
+ ...addition.attrs ?? {}
26015
+ }
26016
+ };
26017
+ continue;
26018
+ }
26019
+ next.push(addition);
26020
+ }
26021
+ return next.length > 0 ? next : void 0;
26022
+ }
26023
+ function getMarkColor(marks, markType) {
26024
+ const mark = marks?.find((candidate) => candidate.type === markType);
26025
+ const color = mark?.attrs?.color;
26026
+ return typeof color === "string" ? color : null;
26027
+ }
26028
+ function replaceTextStyleColor(marks, color) {
26029
+ let replaced = false;
26030
+ const next = (marks ?? []).map((mark) => {
26031
+ if (mark.type !== "textStyle") return mark;
26032
+ replaced = true;
26033
+ return {
26034
+ ...mark,
26035
+ attrs: {
26036
+ ...mark.attrs ?? {},
26037
+ color
26038
+ }
26039
+ };
26040
+ });
26041
+ if (!replaced) {
26042
+ next.unshift({ type: "textStyle", attrs: { color } });
26043
+ }
26044
+ return next;
26045
+ }
26046
+ function ensureReadableSpreadsheetSegments(segments, cellBackgroundColor) {
26047
+ return segments.map((segment) => {
26048
+ const textColor = getMarkColor(segment.marks, "textStyle");
26049
+ if (!isLightTextColor(textColor)) return segment;
26050
+ const inlineBackgroundColor = getMarkColor(segment.marks, "highlight");
26051
+ if (isDarkReadableBackground(inlineBackgroundColor) || isDarkReadableBackground(cellBackgroundColor)) {
26052
+ return segment;
26053
+ }
26054
+ return {
26055
+ ...segment,
26056
+ marks: replaceTextStyleColor(segment.marks, DEFAULT_HTML_TABLE_TEXT_COLOR)
26057
+ };
26058
+ });
26059
+ }
26060
+ function getElementInlineMarks(element, styles) {
26061
+ const marks = [];
26062
+ const tagName = element.tagName;
26063
+ const color = normalizeTextColorValue(styles.get("color") ?? element.getAttribute("color"));
26064
+ const backgroundColor = getBackgroundColor(styles);
26065
+ const fontWeight = styles.get("font-weight")?.toLowerCase();
26066
+ const fontStyle = styles.get("font-style")?.toLowerCase();
26067
+ const textDecoration = styles.get("text-decoration")?.toLowerCase();
26068
+ if (color) {
26069
+ marks.push({ type: "textStyle", attrs: { color } });
26070
+ }
26071
+ if (backgroundColor && !isWhiteColor(backgroundColor)) {
26072
+ marks.push({ type: "highlight", attrs: { color: backgroundColor } });
26073
+ }
26074
+ if (tagName === "B" || tagName === "STRONG" || fontWeight === "bold" || /^\d+$/.test(fontWeight ?? "") && Number(fontWeight) >= 600) {
26075
+ marks.push({ type: "bold" });
26076
+ }
26077
+ if (tagName === "I" || tagName === "EM" || fontStyle === "italic") {
26078
+ marks.push({ type: "italic" });
26079
+ }
26080
+ if (tagName === "U" || textDecoration?.includes("underline")) {
26081
+ marks.push({ type: "underline" });
26082
+ }
26083
+ return marks.length > 0 ? marks : void 0;
26084
+ }
26085
+ function appendTextSegment(segments, segment) {
26086
+ if (!segment.text) return;
26087
+ const lastSegment = segments[segments.length - 1];
26088
+ if (lastSegment && marksEqual(lastSegment.marks, segment.marks)) {
26089
+ lastSegment.text += segment.text;
26090
+ return;
26091
+ }
26092
+ segments.push(segment);
26093
+ }
26094
+ function segmentsEndWithNewline(segments) {
26095
+ return segments.length > 0 && segments[segments.length - 1].text.endsWith("\n");
26096
+ }
26097
+ function normalizeClipboardTextSegments(segments) {
26098
+ const normalizedSegments = [];
26099
+ for (const segment of segments) {
26100
+ appendTextSegment(normalizedSegments, {
26101
+ text: segment.text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\u00a0/g, " "),
26102
+ marks: segment.marks
26103
+ });
26104
+ }
26105
+ while (normalizedSegments.length > 0) {
26106
+ const firstSegment = normalizedSegments[0];
26107
+ firstSegment.text = firstSegment.text.replace(/^\s+/, "");
26108
+ if (firstSegment.text) break;
26109
+ normalizedSegments.shift();
26110
+ }
26111
+ while (normalizedSegments.length > 0) {
26112
+ const lastSegment = normalizedSegments[normalizedSegments.length - 1];
26113
+ lastSegment.text = lastSegment.text.replace(/\s+$/, "");
26114
+ if (lastSegment.text) break;
26115
+ normalizedSegments.pop();
26116
+ }
26117
+ return normalizedSegments;
26118
+ }
25704
26119
  function getClipboardCellText(node) {
25705
26120
  if (node.nodeType === Node.TEXT_NODE) {
25706
26121
  return node.textContent ?? "";
@@ -25718,57 +26133,205 @@ function getClipboardCellText(node) {
25718
26133
  }
25719
26134
  return childText;
25720
26135
  }
25721
- function getHtmlTableRows(table) {
26136
+ function getClipboardCellSegments(node, styleMap, inheritedMarks) {
26137
+ if (node.nodeType === Node.TEXT_NODE) {
26138
+ return [{ text: node.textContent ?? "", marks: inheritedMarks }];
26139
+ }
26140
+ if (!(node instanceof HTMLElement)) {
26141
+ return [];
26142
+ }
26143
+ if (node.tagName === "BR") {
26144
+ return [{ text: "\n", marks: inheritedMarks }];
26145
+ }
26146
+ const styles = getElementStyleDeclarations(node, styleMap);
26147
+ const marks = mergeMarks(inheritedMarks, getElementInlineMarks(node, styles));
26148
+ const segments = [];
26149
+ for (const childNode of Array.from(node.childNodes)) {
26150
+ for (const segment of getClipboardCellSegments(childNode, styleMap, marks)) {
26151
+ appendTextSegment(segments, segment);
26152
+ }
26153
+ }
26154
+ if ((node.tagName === "P" || node.tagName === "DIV" || node.tagName === "LI") && segments.length > 0 && !segmentsEndWithNewline(segments)) {
26155
+ appendTextSegment(segments, { text: "\n" });
26156
+ }
26157
+ return segments;
26158
+ }
26159
+ function getClipboardCellChildSegments(cell, styleMap, inheritedMarks) {
26160
+ const segments = [];
26161
+ for (const childNode of Array.from(cell.childNodes)) {
26162
+ for (const segment of getClipboardCellSegments(childNode, styleMap, inheritedMarks)) {
26163
+ appendTextSegment(segments, segment);
26164
+ }
26165
+ }
26166
+ return normalizeClipboardTextSegments(segments);
26167
+ }
26168
+ function getHtmlTableRows(table, styleMap) {
25722
26169
  const rows = Array.from(table.querySelectorAll("tr")).map(
25723
- (row) => Array.from(row.children).filter((cell) => cell instanceof HTMLTableCellElement).map((cell) => ({
25724
- text: normalizeClipboardCellText(getClipboardCellText(cell)),
25725
- isHeader: cell.tagName === "TH"
25726
- }))
26170
+ (row) => ({
26171
+ attrs: getTableRowAttrs(row, getElementStyleDeclarations(row, styleMap)),
26172
+ cells: Array.from(row.children).filter((cell) => cell instanceof HTMLTableCellElement).map((cell) => {
26173
+ const styles = getElementStyleDeclarations(cell, styleMap);
26174
+ const textColor = normalizeTextColorValue(styles.get("color")) ?? DEFAULT_HTML_TABLE_TEXT_COLOR;
26175
+ const inheritedMarks = [{ type: "textStyle", attrs: { color: textColor } }];
26176
+ const attrs = getTableCellAttrs(cell, styles, DEFAULT_HTML_TABLE_CELL_BACKGROUND_COLOR);
26177
+ const segments = ensureReadableSpreadsheetSegments(
26178
+ getClipboardCellChildSegments(cell, styleMap, inheritedMarks),
26179
+ attrs?.backgroundColor
26180
+ );
26181
+ return {
26182
+ text: normalizeClipboardCellText(getClipboardCellText(cell)),
26183
+ isHeader: cell.tagName === "TH",
26184
+ attrs,
26185
+ segments: segments.length > 0 ? segments : void 0,
26186
+ textColor
26187
+ };
26188
+ })
26189
+ })
25727
26190
  );
25728
- return rows.filter((row) => row.length > 0);
26191
+ return rows.filter((row) => row.cells.length > 0);
26192
+ }
26193
+ function createTextMarks(cell) {
26194
+ return cell.textColor ? [{ type: "textStyle", attrs: { color: cell.textColor } }] : void 0;
25729
26195
  }
25730
- function createParagraphContent(text) {
26196
+ function createParagraphContent(text, marks) {
25731
26197
  return text ? {
25732
26198
  type: "paragraph",
25733
- content: [{ type: "text", text }]
26199
+ content: [{ type: "text", text, ...marks ? { marks } : {} }]
25734
26200
  } : { type: "paragraph" };
25735
26201
  }
26202
+ function createParagraphContentFromSegments(segments) {
26203
+ const paragraphs = [[]];
26204
+ for (const segment of segments) {
26205
+ const parts = segment.text.split("\n");
26206
+ parts.forEach((part, index) => {
26207
+ if (part) {
26208
+ paragraphs[paragraphs.length - 1].push({ text: part, marks: segment.marks });
26209
+ }
26210
+ if (index < parts.length - 1) {
26211
+ paragraphs.push([]);
26212
+ }
26213
+ });
26214
+ }
26215
+ return paragraphs.map((paragraphSegments) => {
26216
+ const content = paragraphSegments.map((segment) => ({
26217
+ type: "text",
26218
+ text: segment.text,
26219
+ ...segment.marks ? { marks: segment.marks } : {}
26220
+ }));
26221
+ return content.length > 0 ? { type: "paragraph", content } : { type: "paragraph" };
26222
+ });
26223
+ }
25736
26224
  function createTableCellContent(cell) {
25737
26225
  const lines = cell.text.split("\n");
25738
- const paragraphs = (lines.length > 0 ? lines : [""]).map(createParagraphContent);
26226
+ const marks = createTextMarks(cell);
26227
+ const paragraphs = cell.segments && cell.segments.length > 0 ? createParagraphContentFromSegments(cell.segments) : (lines.length > 0 ? lines : [""]).map((line) => createParagraphContent(line, marks));
25739
26228
  return {
25740
26229
  type: cell.isHeader ? "tableHeader" : "tableCell",
26230
+ ...cell.attrs ? { attrs: cell.attrs } : {},
25741
26231
  content: paragraphs.length > 0 ? paragraphs : [{ type: "paragraph" }]
25742
26232
  };
25743
26233
  }
25744
- function createTableContent(rows, minColumnCount = 1) {
25745
- const tableRows = rows.filter((row) => row.length > 0);
26234
+ function getRowspanLimitedCell(cell, remainingRowCount) {
26235
+ const attrs = cell.attrs;
26236
+ if (!attrs?.rowspan || attrs.rowspan <= remainingRowCount) return cell;
26237
+ if (remainingRowCount <= 1) {
26238
+ const { rowspan: _rowspan, ...nextAttrs } = attrs;
26239
+ return {
26240
+ ...cell,
26241
+ attrs: Object.keys(nextAttrs).length > 0 ? nextAttrs : void 0
26242
+ };
26243
+ }
26244
+ return {
26245
+ ...cell,
26246
+ attrs: {
26247
+ ...attrs,
26248
+ rowspan: remainingRowCount
26249
+ }
26250
+ };
26251
+ }
26252
+ function normalizeTableRows(rows) {
26253
+ const positionedRows = [];
26254
+ let rowspans = [];
26255
+ let columnCount = 0;
26256
+ rows.forEach((row, rowIndex) => {
26257
+ const coveredColumns = rowspans.map((span) => span > 0);
26258
+ const nextRowspans = rowspans.map((span) => Math.max(0, span - 1));
26259
+ const positionedCells = [];
26260
+ let columnIndex = 0;
26261
+ for (const rawCell of row.cells) {
26262
+ while (coveredColumns[columnIndex]) columnIndex += 1;
26263
+ const remainingRowCount = rows.length - rowIndex;
26264
+ const cell = getRowspanLimitedCell(rawCell, remainingRowCount);
26265
+ const colspan = Math.max(1, cell.attrs?.colspan ?? 1);
26266
+ const rowspan = Math.max(1, cell.attrs?.rowspan ?? 1);
26267
+ positionedCells.push({ startColumn: columnIndex, colspan, cell });
26268
+ if (rowspan > 1) {
26269
+ for (let offset = 0; offset < colspan; offset += 1) {
26270
+ const spannedColumn = columnIndex + offset;
26271
+ nextRowspans[spannedColumn] = Math.max(nextRowspans[spannedColumn] ?? 0, rowspan - 1);
26272
+ }
26273
+ }
26274
+ columnIndex += colspan;
26275
+ }
26276
+ const lastCoveredColumn = coveredColumns.reduce((lastIndex, covered, index) => covered ? index : lastIndex, -1);
26277
+ const lastFutureRowspanColumn = nextRowspans.reduce((lastIndex, span, index) => span > 0 ? index : lastIndex, -1);
26278
+ columnCount = Math.max(columnCount, columnIndex, lastCoveredColumn + 1, lastFutureRowspanColumn + 1);
26279
+ positionedRows.push({
26280
+ attrs: row.attrs,
26281
+ cells: positionedCells,
26282
+ coveredColumns
26283
+ });
26284
+ rowspans = nextRowspans;
26285
+ });
26286
+ return { positionedRows, columnCount };
26287
+ }
26288
+ function createNormalizedRowContent(row, columnCount, fillerCellAttrs) {
26289
+ const content = [];
26290
+ const cellByStartColumn = new Map(row.cells.map((cell) => [cell.startColumn, cell]));
26291
+ let columnIndex = 0;
26292
+ while (columnIndex < columnCount) {
26293
+ if (row.coveredColumns[columnIndex]) {
26294
+ columnIndex += 1;
26295
+ continue;
26296
+ }
26297
+ const positionedCell = cellByStartColumn.get(columnIndex);
26298
+ if (positionedCell) {
26299
+ content.push(createTableCellContent(positionedCell.cell));
26300
+ columnIndex += positionedCell.colspan;
26301
+ continue;
26302
+ }
26303
+ content.push(createTableCellContent({ text: "", isHeader: false, attrs: fillerCellAttrs }));
26304
+ columnIndex += 1;
26305
+ }
26306
+ return content;
26307
+ }
26308
+ function createTableContent(rows, minColumnCount = 1, fillerCellAttrs) {
26309
+ const tableRows = rows.filter((row) => row.cells.length > 0);
25746
26310
  if (tableRows.length === 0) return null;
25747
- const columnCount = tableRows.reduce((max, row) => Math.max(max, row.length), 0);
26311
+ const { positionedRows, columnCount } = normalizeTableRows(tableRows);
25748
26312
  if (columnCount < minColumnCount) return null;
25749
26313
  return {
25750
26314
  type: "table",
25751
- content: tableRows.map((row) => {
25752
- const normalizedRow = Array.from(
25753
- { length: columnCount },
25754
- (_, index) => row[index] ?? { text: "", isHeader: false }
25755
- );
25756
- return {
25757
- type: "tableRow",
25758
- content: normalizedRow.map(createTableCellContent)
25759
- };
25760
- })
26315
+ content: positionedRows.map((row) => ({
26316
+ type: "tableRow",
26317
+ ...row.attrs ? { attrs: row.attrs } : {},
26318
+ content: createNormalizedRowContent(row, columnCount, fillerCellAttrs)
26319
+ }))
25761
26320
  };
25762
26321
  }
25763
26322
  function getClipboardTableContent(dataTransfer) {
25764
26323
  const html = getClipboardData(dataTransfer, "text/html");
25765
26324
  if (!/<table(?:\s|>)/i.test(html)) return null;
25766
26325
  if (typeof DOMParser === "undefined") return null;
26326
+ const doc = new DOMParser().parseFromString(html, "text/html");
25767
26327
  const fragment = extractClipboardHtmlFragment(html);
25768
- const doc = new DOMParser().parseFromString(fragment, "text/html");
25769
- const table = doc.querySelector("table");
26328
+ const fragmentDoc = new DOMParser().parseFromString(fragment, "text/html");
26329
+ const styleMap = parseClipboardCssClassStyles(doc);
26330
+ const table = fragmentDoc.querySelector("table") ?? doc.querySelector("table");
25770
26331
  if (!(table instanceof HTMLTableElement)) return null;
25771
- return createTableContent(getHtmlTableRows(table));
26332
+ return createTableContent(getHtmlTableRows(table, styleMap), 1, {
26333
+ backgroundColor: DEFAULT_HTML_TABLE_CELL_BACKGROUND_COLOR
26334
+ });
25772
26335
  }
25773
26336
  function parseClipboardTsvRows(text) {
25774
26337
  const rows = [];
@@ -25825,7 +26388,9 @@ function getClipboardTsvTableContent(dataTransfer) {
25825
26388
  if (!text.includes(" ")) return null;
25826
26389
  const rows = parseClipboardTsvRows(text);
25827
26390
  return createTableContent(
25828
- rows.map((row) => row.map((cell) => ({ text: normalizeClipboardCellText(cell), isHeader: false }))),
26391
+ rows.map((row) => ({
26392
+ cells: row.map((cell) => ({ text: normalizeClipboardCellText(cell), isHeader: false }))
26393
+ })),
25829
26394
  2
25830
26395
  );
25831
26396
  }
@@ -27100,6 +27665,46 @@ var CustomTableCell = TableCell2.extend({
27100
27665
  "data-border-width": attributes.borderWidth
27101
27666
  };
27102
27667
  }
27668
+ },
27669
+ cellId: {
27670
+ default: null,
27671
+ parseHTML: (element) => element.getAttribute("data-cell-id") || null,
27672
+ renderHTML: (attributes) => {
27673
+ if (!attributes.cellId) return {};
27674
+ return {
27675
+ "data-cell-id": attributes.cellId
27676
+ };
27677
+ }
27678
+ },
27679
+ numberFormat: {
27680
+ default: null,
27681
+ parseHTML: (element) => element.getAttribute("data-number-format") || null,
27682
+ renderHTML: (attributes) => {
27683
+ if (!attributes.numberFormat) return {};
27684
+ return {
27685
+ "data-number-format": attributes.numberFormat
27686
+ };
27687
+ }
27688
+ },
27689
+ formula: {
27690
+ default: null,
27691
+ parseHTML: (element) => element.getAttribute("data-formula") || null,
27692
+ renderHTML: (attributes) => {
27693
+ if (!attributes.formula) return {};
27694
+ return {
27695
+ "data-formula": attributes.formula
27696
+ };
27697
+ }
27698
+ },
27699
+ computedValue: {
27700
+ default: null,
27701
+ parseHTML: (element) => element.getAttribute("data-computed-value") || null,
27702
+ renderHTML: (attributes) => {
27703
+ if (!attributes.computedValue) return {};
27704
+ return {
27705
+ "data-computed-value": attributes.computedValue
27706
+ };
27707
+ }
27103
27708
  }
27104
27709
  };
27105
27710
  },
@@ -27167,6 +27772,46 @@ var CustomTableHeader = TableHeader2.extend({
27167
27772
  "data-border-width": attributes.borderWidth
27168
27773
  };
27169
27774
  }
27775
+ },
27776
+ cellId: {
27777
+ default: null,
27778
+ parseHTML: (element) => element.getAttribute("data-cell-id") || null,
27779
+ renderHTML: (attributes) => {
27780
+ if (!attributes.cellId) return {};
27781
+ return {
27782
+ "data-cell-id": attributes.cellId
27783
+ };
27784
+ }
27785
+ },
27786
+ numberFormat: {
27787
+ default: null,
27788
+ parseHTML: (element) => element.getAttribute("data-number-format") || null,
27789
+ renderHTML: (attributes) => {
27790
+ if (!attributes.numberFormat) return {};
27791
+ return {
27792
+ "data-number-format": attributes.numberFormat
27793
+ };
27794
+ }
27795
+ },
27796
+ formula: {
27797
+ default: null,
27798
+ parseHTML: (element) => element.getAttribute("data-formula") || null,
27799
+ renderHTML: (attributes) => {
27800
+ if (!attributes.formula) return {};
27801
+ return {
27802
+ "data-formula": attributes.formula
27803
+ };
27804
+ }
27805
+ },
27806
+ computedValue: {
27807
+ default: null,
27808
+ parseHTML: (element) => element.getAttribute("data-computed-value") || null,
27809
+ renderHTML: (attributes) => {
27810
+ if (!attributes.computedValue) return {};
27811
+ return {
27812
+ "data-computed-value": attributes.computedValue
27813
+ };
27814
+ }
27170
27815
  }
27171
27816
  };
27172
27817
  },
@@ -27917,6 +28562,34 @@ function collectChildren(node) {
27917
28562
  function createEmptyCellNode(cellNode) {
27918
28563
  return cellNode.type.createAndFill(cellNode.attrs) ?? cellNode;
27919
28564
  }
28565
+ function createCellCopyForColumnDuplicate(cellNode) {
28566
+ return cellNode.type.create(cellNode.attrs, cellNode.content);
28567
+ }
28568
+ function getTableRows(tableNode) {
28569
+ const rows = [];
28570
+ tableNode.forEach((rowNode, rowOffset) => {
28571
+ const cells = [];
28572
+ rowNode.forEach((cellNode, cellOffset, index) => {
28573
+ cells.push({
28574
+ index,
28575
+ node: cellNode,
28576
+ relativePos: rowOffset + 1 + cellOffset
28577
+ });
28578
+ });
28579
+ rows.push({
28580
+ node: rowNode,
28581
+ cells
28582
+ });
28583
+ });
28584
+ return rows;
28585
+ }
28586
+ function safeFindCell(map, relativePos) {
28587
+ try {
28588
+ return map.findCell(relativePos);
28589
+ } catch {
28590
+ return null;
28591
+ }
28592
+ }
27920
28593
  function getSelectedTableRect(editor) {
27921
28594
  const cellSelection = getCellSelectionPositions(editor.state.selection);
27922
28595
  if (cellSelection) {
@@ -28042,34 +28715,49 @@ function duplicateTableRowAt(editor, rowIndex, cellPos) {
28042
28715
  }
28043
28716
  function clearTableRowAt(editor, rowIndex, cellPos) {
28044
28717
  return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
28045
- const rows = collectChildren(tableNode);
28046
- const rowNode = rows[rowIndex];
28047
- if (!rowNode) return null;
28048
- const cells = collectChildren(rowNode).map((cellNode) => createEmptyCellNode(cellNode));
28049
- rows[rowIndex] = rowNode.type.create(rowNode.attrs, cells);
28718
+ const map = TableMap.get(tableNode);
28719
+ if (rowIndex < 0 || rowIndex >= map.height) return null;
28720
+ const rows = getTableRows(tableNode).map((rowInfo) => {
28721
+ const cells = collectChildren(rowInfo.node);
28722
+ for (const entry of rowInfo.cells) {
28723
+ const rect = safeFindCell(map, entry.relativePos);
28724
+ if (!rect || rect.top > rowIndex || rowIndex >= rect.bottom) continue;
28725
+ cells[entry.index] = createEmptyCellNode(entry.node);
28726
+ }
28727
+ return rowInfo.node.type.create(rowInfo.node.attrs, cells);
28728
+ });
28050
28729
  return tableNode.type.create(tableNode.attrs, rows);
28051
28730
  });
28052
28731
  }
28053
28732
  function duplicateTableColumnAt(editor, columnIndex, cellPos) {
28054
28733
  return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
28055
- const rows = collectChildren(tableNode).map((rowNode) => {
28056
- const cells = collectChildren(rowNode);
28057
- const cellNode = cells[columnIndex];
28058
- if (!cellNode) return rowNode;
28059
- cells.splice(columnIndex + 1, 0, cellNode.copy(cellNode.content));
28060
- return rowNode.type.create(rowNode.attrs, cells);
28734
+ const map = TableMap.get(tableNode);
28735
+ if (columnIndex < 0 || columnIndex >= map.width) return null;
28736
+ const rows = getTableRows(tableNode).map((rowInfo, rowIndex) => {
28737
+ const cells = collectChildren(rowInfo.node);
28738
+ const sourceCell = rowInfo.cells.find((entry) => {
28739
+ const rect = safeFindCell(map, entry.relativePos);
28740
+ return rect && rect.top === rowIndex && rect.left <= columnIndex && columnIndex < rect.right;
28741
+ });
28742
+ if (!sourceCell) return rowInfo.node;
28743
+ cells.splice(sourceCell.index + 1, 0, createCellCopyForColumnDuplicate(sourceCell.node));
28744
+ return rowInfo.node.type.create(rowInfo.node.attrs, cells);
28061
28745
  });
28062
28746
  return tableNode.type.create(tableNode.attrs, rows);
28063
28747
  });
28064
28748
  }
28065
28749
  function clearTableColumnAt(editor, columnIndex, cellPos) {
28066
28750
  return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
28067
- const rows = collectChildren(tableNode).map((rowNode) => {
28068
- const cells = collectChildren(rowNode);
28069
- const cellNode = cells[columnIndex];
28070
- if (!cellNode) return rowNode;
28071
- cells[columnIndex] = createEmptyCellNode(cellNode);
28072
- return rowNode.type.create(rowNode.attrs, cells);
28751
+ const map = TableMap.get(tableNode);
28752
+ if (columnIndex < 0 || columnIndex >= map.width) return null;
28753
+ const rows = getTableRows(tableNode).map((rowInfo) => {
28754
+ const cells = collectChildren(rowInfo.node);
28755
+ for (const entry of rowInfo.cells) {
28756
+ const rect = safeFindCell(map, entry.relativePos);
28757
+ if (!rect || rect.left > columnIndex || columnIndex >= rect.right) continue;
28758
+ cells[entry.index] = createEmptyCellNode(entry.node);
28759
+ }
28760
+ return rowInfo.node.type.create(rowInfo.node.attrs, cells);
28073
28761
  });
28074
28762
  return tableNode.type.create(tableNode.attrs, rows);
28075
28763
  });
@@ -29121,7 +29809,7 @@ var EditorToolbar = ({
29121
29809
  // src/components/UEditor/menus.tsx
29122
29810
  import { useCallback as useCallback22, useEffect as useEffect36, useMemo as useMemo23, useRef as useRef33, useState as useState48 } from "react";
29123
29811
  import { useEditorState as useEditorState2 } from "@tiptap/react";
29124
- import { isInTable as isSelectionInTable, setCellAttr } from "@tiptap/pm/tables";
29812
+ import { isInTable as isSelectionInTable, setCellAttr as setCellAttr2 } from "@tiptap/pm/tables";
29125
29813
  import { createPortal as createPortal8 } from "react-dom";
29126
29814
  import {
29127
29815
  AlignCenter as AlignCenter2,
@@ -29143,13 +29831,410 @@ import {
29143
29831
  Strikethrough as StrikethroughIcon2,
29144
29832
  Edit2,
29145
29833
  Unlink,
29146
- ExternalLink as ExternalLink3
29834
+ ExternalLink as ExternalLink3,
29835
+ Sigma
29147
29836
  } from "lucide-react";
29837
+
29838
+ // src/components/UEditor/table-formula-commands.ts
29839
+ import { selectedRect as selectedRect2, setCellAttr, TableMap as TableMap2 } from "@tiptap/pm/tables";
29840
+
29841
+ // src/components/UEditor/table-formula.ts
29842
+ var CELL_ADDRESS_RE = /^([A-Z]+)([1-9]\d*)$/i;
29843
+ var CELL_RANGE_RE = /^([A-Z]+[1-9]\d*):([A-Z]+[1-9]\d*)$/i;
29844
+ var SUPPORTED_FUNCTIONS = /* @__PURE__ */ new Set(["SUM", "AVG", "MIN", "MAX", "COUNT"]);
29845
+ function columnNameToIndex(columnName) {
29846
+ const normalized = columnName.trim().toUpperCase();
29847
+ if (!/^[A-Z]+$/.test(normalized)) {
29848
+ return -1;
29849
+ }
29850
+ let index = 0;
29851
+ for (const char of normalized) {
29852
+ index = index * 26 + char.charCodeAt(0) - 64;
29853
+ }
29854
+ return index - 1;
29855
+ }
29856
+ function indexToColumnName(index) {
29857
+ if (!Number.isInteger(index) || index < 0) {
29858
+ return "";
29859
+ }
29860
+ let value = index + 1;
29861
+ let name = "";
29862
+ while (value > 0) {
29863
+ const remainder = (value - 1) % 26;
29864
+ name = String.fromCharCode(65 + remainder) + name;
29865
+ value = Math.floor((value - 1) / 26);
29866
+ }
29867
+ return name;
29868
+ }
29869
+ function parseTableCellAddress(input) {
29870
+ const match = input.trim().match(CELL_ADDRESS_RE);
29871
+ if (!match) {
29872
+ return null;
29873
+ }
29874
+ const column = columnNameToIndex(match[1] ?? "");
29875
+ const row = Number.parseInt(match[2] ?? "", 10) - 1;
29876
+ if (column < 0 || row < 0) {
29877
+ return null;
29878
+ }
29879
+ return {
29880
+ column,
29881
+ row,
29882
+ label: `${indexToColumnName(column)}${row + 1}`
29883
+ };
29884
+ }
29885
+ function parseTableCellRange(input) {
29886
+ const match = input.trim().match(CELL_RANGE_RE);
29887
+ if (!match) {
29888
+ return null;
29889
+ }
29890
+ const from = parseTableCellAddress(match[1] ?? "");
29891
+ const to = parseTableCellAddress(match[2] ?? "");
29892
+ if (!from || !to) {
29893
+ return null;
29894
+ }
29895
+ return { from, to };
29896
+ }
29897
+ function getTableCellRangeLabels(range) {
29898
+ const startColumn = Math.min(range.from.column, range.to.column);
29899
+ const endColumn = Math.max(range.from.column, range.to.column);
29900
+ const startRow = Math.min(range.from.row, range.to.row);
29901
+ const endRow = Math.max(range.from.row, range.to.row);
29902
+ const labels = [];
29903
+ for (let row = startRow; row <= endRow; row += 1) {
29904
+ for (let column = startColumn; column <= endColumn; column += 1) {
29905
+ labels.push(`${indexToColumnName(column)}${row + 1}`);
29906
+ }
29907
+ }
29908
+ return labels;
29909
+ }
29910
+ function normalizeTableFormula(formula) {
29911
+ return formula.trim().replace(/^=/, "").trim();
29912
+ }
29913
+ function evaluateBasicTableFormula(formula, getCellValue) {
29914
+ const normalized = normalizeTableFormula(formula);
29915
+ if (!normalized) {
29916
+ return { value: null, error: "empty" };
29917
+ }
29918
+ const tokens = tokenizeFormula(normalized);
29919
+ if (!tokens) {
29920
+ return { value: null, error: "invalid-formula" };
29921
+ }
29922
+ const parser = new FormulaParser(tokens, getCellValue);
29923
+ const result = parser.parseExpression();
29924
+ if (result.error) {
29925
+ return result;
29926
+ }
29927
+ if (!parser.isComplete()) {
29928
+ return { value: null, error: "invalid-formula" };
29929
+ }
29930
+ return result;
29931
+ }
29932
+ function tokenizeFormula(formula) {
29933
+ const tokens = [];
29934
+ let index = 0;
29935
+ while (index < formula.length) {
29936
+ const char = formula[index];
29937
+ if (!char) break;
29938
+ if (/\s/.test(char)) {
29939
+ index += 1;
29940
+ continue;
29941
+ }
29942
+ if (char === "," || char === "+" || char === "-" || char === "*" || char === "/" || char === "(" || char === ")") {
29943
+ if (char === ",") tokens.push({ type: "comma", value: char });
29944
+ else if (char === "(" || char === ")") tokens.push({ type: "paren", value: char });
29945
+ else tokens.push({ type: "operator", value: char });
29946
+ index += 1;
29947
+ continue;
29948
+ }
29949
+ const numberMatch = formula.slice(index).match(/^\d+(?:\.\d+)?/);
29950
+ if (numberMatch?.[0]) {
29951
+ tokens.push({ type: "number", value: Number.parseFloat(numberMatch[0]) });
29952
+ index += numberMatch[0].length;
29953
+ continue;
29954
+ }
29955
+ const identifierMatch = formula.slice(index).match(/^[A-Z]+[1-9]\d*(?::[A-Z]+[1-9]\d*)?|^[A-Z]+/i);
29956
+ if (identifierMatch?.[0]) {
29957
+ const value = identifierMatch[0].toUpperCase();
29958
+ if (CELL_RANGE_RE.test(value)) tokens.push({ type: "range", value });
29959
+ else if (parseTableCellAddress(value)) tokens.push({ type: "cell", value });
29960
+ else if (SUPPORTED_FUNCTIONS.has(value)) tokens.push({ type: "function", value });
29961
+ else return null;
29962
+ index += identifierMatch[0].length;
29963
+ continue;
29964
+ }
29965
+ return null;
29966
+ }
29967
+ return tokens;
29968
+ }
29969
+ var FormulaParser = class {
29970
+ constructor(tokens, getCellValue) {
29971
+ this.tokens = tokens;
29972
+ this.getCellValue = getCellValue;
29973
+ this.index = 0;
29974
+ }
29975
+ isComplete() {
29976
+ return this.index >= this.tokens.length;
29977
+ }
29978
+ parseExpression() {
29979
+ let left = this.parseTerm();
29980
+ while (!left.error) {
29981
+ const operator = this.peekOperator(["+", "-"]);
29982
+ if (!operator) break;
29983
+ this.index += 1;
29984
+ const right = this.parseTerm();
29985
+ if (right.error) return right;
29986
+ left = {
29987
+ value: operator.value === "+" ? left.value + right.value : left.value - right.value,
29988
+ error: null
29989
+ };
29990
+ }
29991
+ return left;
29992
+ }
29993
+ parseTerm() {
29994
+ let left = this.parseFactor();
29995
+ while (!left.error) {
29996
+ const operator = this.peekOperator(["*", "/"]);
29997
+ if (!operator) break;
29998
+ this.index += 1;
29999
+ const right = this.parseFactor();
30000
+ if (right.error) return right;
30001
+ if (operator.value === "/" && right.value === 0) {
30002
+ return { value: null, error: "division-by-zero" };
30003
+ }
30004
+ left = {
30005
+ value: operator.value === "*" ? left.value * right.value : left.value / right.value,
30006
+ error: null
30007
+ };
30008
+ }
30009
+ return left;
30010
+ }
30011
+ parseFactor() {
30012
+ const token = this.tokens[this.index];
30013
+ if (!token) {
30014
+ return { value: null, error: "invalid-formula" };
30015
+ }
30016
+ if (token.type === "operator" && token.value === "-") {
30017
+ this.index += 1;
30018
+ const value = this.parseFactor();
30019
+ if (value.error) return value;
30020
+ return { value: -value.value, error: null };
30021
+ }
30022
+ if (token.type === "number") {
30023
+ this.index += 1;
30024
+ return { value: token.value, error: null };
30025
+ }
30026
+ if (token.type === "cell") {
30027
+ this.index += 1;
30028
+ return this.readCellNumber(token.value);
30029
+ }
30030
+ if (token.type === "function") {
30031
+ return this.parseFunction(token.value);
30032
+ }
30033
+ if (token.type === "paren" && token.value === "(") {
30034
+ this.index += 1;
30035
+ const value = this.parseExpression();
30036
+ if (value.error) return value;
30037
+ if (!this.consumeParen(")")) {
30038
+ return { value: null, error: "invalid-formula" };
30039
+ }
30040
+ return value;
30041
+ }
30042
+ return { value: null, error: "invalid-formula" };
30043
+ }
30044
+ parseFunction(name) {
30045
+ this.index += 1;
30046
+ if (!this.consumeParen("(")) {
30047
+ return { value: null, error: "invalid-formula" };
30048
+ }
30049
+ const values = [];
30050
+ while (true) {
30051
+ const token = this.tokens[this.index];
30052
+ if (!token) {
30053
+ return { value: null, error: "invalid-formula" };
30054
+ }
30055
+ if (token.type === "range") {
30056
+ this.index += 1;
30057
+ const range = parseTableCellRange(token.value);
30058
+ if (!range) return { value: null, error: "invalid-reference" };
30059
+ for (const label of getTableCellRangeLabels(range)) {
30060
+ const cellValue = this.readCellNumber(label);
30061
+ if (cellValue.error) return cellValue;
30062
+ values.push(cellValue.value);
30063
+ }
30064
+ } else {
30065
+ const value = this.parseExpression();
30066
+ if (value.error) return value;
30067
+ values.push(value.value);
30068
+ }
30069
+ if (this.consumeComma()) {
30070
+ continue;
30071
+ }
30072
+ if (this.consumeParen(")")) {
30073
+ break;
30074
+ }
30075
+ return { value: null, error: "invalid-formula" };
30076
+ }
30077
+ if (values.length === 0) {
30078
+ return { value: null, error: "invalid-formula" };
30079
+ }
30080
+ if (name === "SUM") return { value: values.reduce((sum, value) => sum + value, 0), error: null };
30081
+ if (name === "AVG") return { value: values.reduce((sum, value) => sum + value, 0) / values.length, error: null };
30082
+ if (name === "MIN") return { value: Math.min(...values), error: null };
30083
+ if (name === "MAX") return { value: Math.max(...values), error: null };
30084
+ if (name === "COUNT") return { value: values.length, error: null };
30085
+ return { value: null, error: "invalid-formula" };
30086
+ }
30087
+ readCellNumber(label) {
30088
+ const value = this.getCellValue(label);
30089
+ const parsed = typeof value === "number" ? value : Number.parseFloat(String(value ?? "").trim());
30090
+ if (!Number.isFinite(parsed)) {
30091
+ return { value: null, error: "invalid-reference" };
30092
+ }
30093
+ return { value: parsed, error: null };
30094
+ }
30095
+ peekOperator(operators) {
30096
+ const token = this.tokens[this.index];
30097
+ return token?.type === "operator" && operators.includes(token.value) ? token : null;
30098
+ }
30099
+ consumeComma() {
30100
+ if (this.tokens[this.index]?.type !== "comma") {
30101
+ return false;
30102
+ }
30103
+ this.index += 1;
30104
+ return true;
30105
+ }
30106
+ consumeParen(value) {
30107
+ const token = this.tokens[this.index];
30108
+ if (token?.type !== "paren" || token.value !== value) {
30109
+ return false;
30110
+ }
30111
+ this.index += 1;
30112
+ return true;
30113
+ }
30114
+ };
30115
+
30116
+ // src/components/UEditor/table-formula-commands.ts
30117
+ function collectChildren2(node) {
30118
+ const children = [];
30119
+ node.forEach((child) => children.push(child));
30120
+ return children;
30121
+ }
30122
+ function getTableRows2(tableNode) {
30123
+ const rows = [];
30124
+ tableNode.forEach((rowNode, rowOffset) => {
30125
+ const cells = [];
30126
+ rowNode.forEach((cellNode, cellOffset, index) => {
30127
+ cells.push({
30128
+ index,
30129
+ node: cellNode,
30130
+ relativePos: rowOffset + 1 + cellOffset
30131
+ });
30132
+ });
30133
+ rows.push({ node: rowNode, cells });
30134
+ });
30135
+ return rows;
30136
+ }
30137
+ function safeFindCell2(map, relativePos) {
30138
+ try {
30139
+ return map.findCell(relativePos);
30140
+ } catch {
30141
+ return null;
30142
+ }
30143
+ }
30144
+ function getCellText(cellNode) {
30145
+ return cellNode.textBetween(0, cellNode.content.size, "\n").trim();
30146
+ }
30147
+ function buildTableValueGetter(tableNode) {
30148
+ const map = TableMap2.get(tableNode);
30149
+ const values = /* @__PURE__ */ new Map();
30150
+ for (const rowInfo of getTableRows2(tableNode)) {
30151
+ for (const entry of rowInfo.cells) {
30152
+ const rect = safeFindCell2(map, entry.relativePos);
30153
+ if (!rect) continue;
30154
+ const label = `${indexToColumnName(rect.left)}${rect.top + 1}`;
30155
+ const computedValue = entry.node.attrs.computedValue;
30156
+ values.set(label, typeof computedValue === "string" && computedValue.trim() ? computedValue : getCellText(entry.node));
30157
+ }
30158
+ }
30159
+ return (label) => values.get(label.toUpperCase());
30160
+ }
30161
+ function getFormulaComputedValue(formula, tableNode) {
30162
+ const result = evaluateBasicTableFormula(formula, buildTableValueGetter(tableNode));
30163
+ return result.error ? `#${result.error.toUpperCase()}` : String(result.value);
30164
+ }
30165
+ function normalizeFormulaInput(formula) {
30166
+ const trimmed = formula.trim();
30167
+ if (!trimmed) return "";
30168
+ return trimmed.startsWith("=") ? trimmed : `=${trimmed}`;
30169
+ }
30170
+ function setSelectedTableCellFormula(editor, formula) {
30171
+ const normalized = normalizeFormulaInput(formula);
30172
+ const { state, view } = editor;
30173
+ if (!normalized) {
30174
+ const clearedFormula = setCellAttr("formula", null)(state, view.dispatch.bind(view));
30175
+ const clearedValue = setCellAttr("computedValue", null)(editor.state, view.dispatch.bind(view));
30176
+ if (clearedFormula || clearedValue) {
30177
+ view.focus();
30178
+ dispatchTableLayoutChange(editor);
30179
+ return true;
30180
+ }
30181
+ return false;
30182
+ }
30183
+ const rect = selectedRect2(state);
30184
+ const computedValue = getFormulaComputedValue(normalized, rect.table);
30185
+ const appliedFormula = setCellAttr("formula", normalized)(state, view.dispatch.bind(view));
30186
+ const appliedValue = setCellAttr("computedValue", computedValue)(editor.state, view.dispatch.bind(view));
30187
+ if (appliedFormula || appliedValue) {
30188
+ view.focus();
30189
+ dispatchTableLayoutChange(editor);
30190
+ return true;
30191
+ }
30192
+ return false;
30193
+ }
30194
+ function clearSelectedTableCellFormula(editor) {
30195
+ return setSelectedTableCellFormula(editor, "");
30196
+ }
30197
+ function recalculateSelectedTable(editor) {
30198
+ const rect = selectedRect2(editor.state);
30199
+ const tableNode = rect.table;
30200
+ const map = TableMap2.get(tableNode);
30201
+ const getCellValue = buildTableValueGetter(tableNode);
30202
+ let changed = false;
30203
+ const rows = getTableRows2(tableNode).map((rowInfo) => {
30204
+ const cells = collectChildren2(rowInfo.node);
30205
+ for (const entry of rowInfo.cells) {
30206
+ const formula = typeof entry.node.attrs.formula === "string" ? entry.node.attrs.formula.trim() : "";
30207
+ if (!formula) continue;
30208
+ const rectForCell = safeFindCell2(map, entry.relativePos);
30209
+ if (!rectForCell) continue;
30210
+ const result = evaluateBasicTableFormula(formula, getCellValue);
30211
+ const computedValue = result.error ? `#${result.error.toUpperCase()}` : String(result.value);
30212
+ if (entry.node.attrs.computedValue === computedValue) continue;
30213
+ cells[entry.index] = entry.node.type.create(
30214
+ {
30215
+ ...entry.node.attrs,
30216
+ computedValue
30217
+ },
30218
+ entry.node.content,
30219
+ entry.node.marks
30220
+ );
30221
+ changed = true;
30222
+ }
30223
+ return rowInfo.node.type.create(rowInfo.node.attrs, cells);
30224
+ });
30225
+ if (!changed) return false;
30226
+ const nextTable = tableNode.type.create(tableNode.attrs, rows);
30227
+ editor.view.dispatch(editor.state.tr.replaceWith(rect.tableStart - 1, rect.tableStart - 1 + tableNode.nodeSize, nextTable));
30228
+ dispatchTableLayoutChange(editor);
30229
+ return true;
30230
+ }
30231
+
30232
+ // src/components/UEditor/menus.tsx
29148
30233
  import { Fragment as Fragment29, jsx as jsx86, jsxs as jsxs72 } from "react/jsx-runtime";
29149
30234
  function applyTableCellBackground(editor, color) {
29150
30235
  const value = color || null;
29151
30236
  const { state, view } = editor;
29152
- const applied = setCellAttr("backgroundColor", value)(state, view.dispatch.bind(view));
30237
+ const applied = setCellAttr2("backgroundColor", value)(state, view.dispatch.bind(view));
29153
30238
  if (applied) {
29154
30239
  view.focus();
29155
30240
  return;
@@ -29175,6 +30260,8 @@ var BubbleMenuContent = ({
29175
30260
  setShowLinkInput(initialShowLinkInput);
29176
30261
  }, [initialShowLinkInput]);
29177
30262
  const [showTypographyPanel, setShowTypographyPanel] = useState48(false);
30263
+ const [showFormulaPanel, setShowFormulaPanel] = useState48(false);
30264
+ const [formulaDraft, setFormulaDraft] = useState48("");
29178
30265
  const [showFontSizeOptions, setShowFontSizeOptions] = useState48(false);
29179
30266
  const [fontSizeDraft, setFontSizeDraft] = useState48("");
29180
30267
  const isImageSelected = editor.isActive("image");
@@ -29185,6 +30272,7 @@ var BubbleMenuContent = ({
29185
30272
  const currentTextColor = normalizeStyleValue(textStyleAttrs.color) || "inherit";
29186
30273
  const currentHighlightColor = normalizeStyleValue(editor.getAttributes("highlight").color) || "";
29187
30274
  const currentCellBgColor = normalizeStyleValue(editor.getAttributes("tableCell").backgroundColor || editor.getAttributes("tableHeader").backgroundColor) || "";
30275
+ const currentCellFormula = normalizeStyleValue(editor.getAttributes("tableCell").formula || editor.getAttributes("tableHeader").formula) || "";
29188
30276
  const isInTable2 = isSelectionInTable(editor.state);
29189
30277
  const canMergeCells = isInTable2 && editor.can().mergeCells();
29190
30278
  const canSplitCell = isInTable2 && editor.can().splitCell();
@@ -29201,6 +30289,9 @@ var BubbleMenuContent = ({
29201
30289
  useEffect36(() => {
29202
30290
  setFontSizeDraft(currentFontSize.replace(/px$/i, ""));
29203
30291
  }, [currentFontSize]);
30292
+ useEffect36(() => {
30293
+ setFormulaDraft(currentCellFormula);
30294
+ }, [currentCellFormula]);
29204
30295
  const applyFontSizeDraft = () => {
29205
30296
  const normalized = fontSizeDraft.trim();
29206
30297
  if (!normalized) {
@@ -29403,6 +30494,82 @@ var BubbleMenuContent = ({
29403
30494
  )
29404
30495
  ] });
29405
30496
  }
30497
+ if (showFormulaPanel && isInTable2) {
30498
+ return /* @__PURE__ */ jsxs72("div", { className: "w-72 p-2", children: [
30499
+ /* @__PURE__ */ jsxs72("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
30500
+ /* @__PURE__ */ jsx86("span", { className: "px-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: t("tableMenu.formula") || "Formula" }),
30501
+ /* @__PURE__ */ jsx86(
30502
+ "button",
30503
+ {
30504
+ type: "button",
30505
+ onClick: () => {
30506
+ setShowFormulaPanel(false);
30507
+ onKeepOpenChange?.(false);
30508
+ },
30509
+ className: "rounded-md px-2 py-1 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
30510
+ children: t("colors.done")
30511
+ }
30512
+ )
30513
+ ] }),
30514
+ /* @__PURE__ */ jsx86("div", { className: "flex h-9 items-center overflow-hidden rounded-md border border-border/60 bg-muted/40", children: /* @__PURE__ */ jsx86(
30515
+ "input",
30516
+ {
30517
+ value: formulaDraft,
30518
+ onChange: (event) => setFormulaDraft(event.target.value),
30519
+ onMouseDown: (event) => event.stopPropagation(),
30520
+ onClick: (event) => event.stopPropagation(),
30521
+ onKeyDown: (event) => {
30522
+ event.stopPropagation();
30523
+ if (event.key === "Enter") {
30524
+ event.preventDefault();
30525
+ setSelectedTableCellFormula(editor, formulaDraft);
30526
+ setShowFormulaPanel(false);
30527
+ onKeepOpenChange?.(false);
30528
+ }
30529
+ },
30530
+ "aria-label": t("tableMenu.formula") || "Formula",
30531
+ placeholder: "=SUM(A1:A3)",
30532
+ className: "h-full min-w-0 flex-1 bg-transparent px-2 text-sm font-medium text-foreground outline-none"
30533
+ }
30534
+ ) }),
30535
+ /* @__PURE__ */ jsxs72("div", { className: "mt-2 grid grid-cols-3 gap-1", children: [
30536
+ /* @__PURE__ */ jsx86(
30537
+ "button",
30538
+ {
30539
+ type: "button",
30540
+ onClick: () => {
30541
+ setSelectedTableCellFormula(editor, formulaDraft);
30542
+ setShowFormulaPanel(false);
30543
+ onKeepOpenChange?.(false);
30544
+ },
30545
+ className: "h-8 rounded-md bg-primary/10 text-xs font-semibold text-primary transition-colors hover:bg-primary/15",
30546
+ children: t("tableMenu.apply") || "Apply"
30547
+ }
30548
+ ),
30549
+ /* @__PURE__ */ jsx86(
30550
+ "button",
30551
+ {
30552
+ type: "button",
30553
+ onClick: () => {
30554
+ clearSelectedTableCellFormula(editor);
30555
+ setFormulaDraft("");
30556
+ },
30557
+ className: "h-8 rounded-md bg-muted/40 text-xs font-semibold text-foreground transition-colors hover:bg-muted",
30558
+ children: t("tableMenu.clear") || "Clear"
30559
+ }
30560
+ ),
30561
+ /* @__PURE__ */ jsx86(
30562
+ "button",
30563
+ {
30564
+ type: "button",
30565
+ onClick: () => recalculateSelectedTable(editor),
30566
+ className: "h-8 rounded-md bg-muted/40 text-xs font-semibold text-foreground transition-colors hover:bg-muted",
30567
+ children: t("tableMenu.recalculate") || "Recalc"
30568
+ }
30569
+ )
30570
+ ] })
30571
+ ] });
30572
+ }
29406
30573
  if (showTypographyPanel) {
29407
30574
  return /* @__PURE__ */ jsxs72("div", { className: "w-72 p-2", children: [
29408
30575
  /* @__PURE__ */ jsxs72("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
@@ -29616,6 +30783,21 @@ var BubbleMenuContent = ({
29616
30783
  )
29617
30784
  }
29618
30785
  ),
30786
+ /* @__PURE__ */ jsx86(
30787
+ ToolbarButton,
30788
+ {
30789
+ onMouseDown: () => {
30790
+ onKeepOpenChange?.(true);
30791
+ },
30792
+ onClick: () => {
30793
+ setFormulaDraft(currentCellFormula);
30794
+ setShowFormulaPanel(true);
30795
+ },
30796
+ active: Boolean(currentCellFormula),
30797
+ title: t("tableMenu.formula") || "Formula",
30798
+ children: /* @__PURE__ */ jsx86(Sigma, { className: "w-4 h-4" })
30799
+ }
30800
+ ),
29619
30801
  /* @__PURE__ */ jsx86(
29620
30802
  ToolbarButton,
29621
30803
  {
@@ -33457,7 +34639,7 @@ if (typeof WeakMap != "undefined") {
33457
34639
  return cache[cachePos++] = value;
33458
34640
  };
33459
34641
  }
33460
- var TableMap2 = class {
34642
+ var TableMap3 = class {
33461
34643
  constructor(width, height, map, problems) {
33462
34644
  this.width = width;
33463
34645
  this.height = height;
@@ -33594,7 +34776,7 @@ function computeMap(table) {
33594
34776
  pos++;
33595
34777
  }
33596
34778
  if (width === 0 || height === 0) (problems || (problems = [])).push({ type: "zero_sized" });
33597
- const tableMap = new TableMap2(width, height, map, problems);
34779
+ const tableMap = new TableMap3(width, height, map, problems);
33598
34780
  let badWidths = false;
33599
34781
  for (let i = 0; !badWidths && i < colWidths.length; i += 2) if (colWidths[i] != null && colWidths[i + 1] < height) badWidths = true;
33600
34782
  if (badWidths) findBadColWidths(tableMap, colWidths, table);
@@ -33698,7 +34880,7 @@ function inSameTable($cellA, $cellB) {
33698
34880
  }
33699
34881
  function nextCell($pos, axis, dir) {
33700
34882
  const table = $pos.node(-1);
33701
- const map = TableMap2.get(table);
34883
+ const map = TableMap3.get(table);
33702
34884
  const tableStart = $pos.start(-1);
33703
34885
  const moved = map.nextCell($pos.pos - tableStart, axis, dir);
33704
34886
  return moved == null ? null : $pos.node(0).resolve(tableStart + moved);
@@ -33718,7 +34900,7 @@ function removeColSpan(attrs, pos, n = 1) {
33718
34900
  var CellSelection = class CellSelection2 extends Selection {
33719
34901
  constructor($anchorCell, $headCell = $anchorCell) {
33720
34902
  const table = $anchorCell.node(-1);
33721
- const map = TableMap2.get(table);
34903
+ const map = TableMap3.get(table);
33722
34904
  const tableStart = $anchorCell.start(-1);
33723
34905
  const rect = map.rectBetween($anchorCell.pos - tableStart, $headCell.pos - tableStart);
33724
34906
  const doc = $anchorCell.node(0);
@@ -33747,7 +34929,7 @@ var CellSelection = class CellSelection2 extends Selection {
33747
34929
  }
33748
34930
  content() {
33749
34931
  const table = this.$anchorCell.node(-1);
33750
- const map = TableMap2.get(table);
34932
+ const map = TableMap3.get(table);
33751
34933
  const tableStart = this.$anchorCell.start(-1);
33752
34934
  const rect = map.rectBetween(this.$anchorCell.pos - tableStart, this.$headCell.pos - tableStart);
33753
34935
  const seen = {};
@@ -33801,7 +34983,7 @@ var CellSelection = class CellSelection2 extends Selection {
33801
34983
  }
33802
34984
  forEachCell(f) {
33803
34985
  const table = this.$anchorCell.node(-1);
33804
- const map = TableMap2.get(table);
34986
+ const map = TableMap3.get(table);
33805
34987
  const tableStart = this.$anchorCell.start(-1);
33806
34988
  const cells = map.cellsInRect(map.rectBetween(this.$anchorCell.pos - tableStart, this.$headCell.pos - tableStart));
33807
34989
  for (let i = 0; i < cells.length; i++) f(table.nodeAt(cells[i]), tableStart + cells[i]);
@@ -33816,7 +34998,7 @@ var CellSelection = class CellSelection2 extends Selection {
33816
34998
  }
33817
34999
  static colSelection($anchorCell, $headCell = $anchorCell) {
33818
35000
  const table = $anchorCell.node(-1);
33819
- const map = TableMap2.get(table);
35001
+ const map = TableMap3.get(table);
33820
35002
  const tableStart = $anchorCell.start(-1);
33821
35003
  const anchorRect = map.findCell($anchorCell.pos - tableStart);
33822
35004
  const headRect = map.findCell($headCell.pos - tableStart);
@@ -33832,7 +35014,7 @@ var CellSelection = class CellSelection2 extends Selection {
33832
35014
  }
33833
35015
  isRowSelection() {
33834
35016
  const table = this.$anchorCell.node(-1);
33835
- const map = TableMap2.get(table);
35017
+ const map = TableMap3.get(table);
33836
35018
  const tableStart = this.$anchorCell.start(-1);
33837
35019
  const anchorLeft = map.colCount(this.$anchorCell.pos - tableStart);
33838
35020
  const headLeft = map.colCount(this.$headCell.pos - tableStart);
@@ -33846,7 +35028,7 @@ var CellSelection = class CellSelection2 extends Selection {
33846
35028
  }
33847
35029
  static rowSelection($anchorCell, $headCell = $anchorCell) {
33848
35030
  const table = $anchorCell.node(-1);
33849
- const map = TableMap2.get(table);
35031
+ const map = TableMap3.get(table);
33850
35032
  const tableStart = $anchorCell.start(-1);
33851
35033
  const anchorRect = map.findCell($anchorCell.pos - tableStart);
33852
35034
  const headRect = map.findCell($headCell.pos - tableStart);
@@ -33895,7 +35077,7 @@ var CellBookmark = class CellBookmark2 {
33895
35077
  };
33896
35078
  var fixTablesKey = new PluginKey4("fix-tables");
33897
35079
  function convertTableNodeToArrayOfRows(tableNode) {
33898
- const map = TableMap2.get(tableNode);
35080
+ const map = TableMap3.get(tableNode);
33899
35081
  const rows = [];
33900
35082
  const rowCount = map.height;
33901
35083
  const colCount$1 = map.width;
@@ -33926,7 +35108,7 @@ function convertTableNodeToArrayOfRows(tableNode) {
33926
35108
  }
33927
35109
  function convertArrayOfRowsToTableNode(tableNode, arrayOfNodes) {
33928
35110
  const newRows = [];
33929
- const map = TableMap2.get(tableNode);
35111
+ const map = TableMap3.get(tableNode);
33930
35112
  const rowCount = map.height;
33931
35113
  const colCount$1 = map.width;
33932
35114
  for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
@@ -33975,7 +35157,7 @@ function findParentNode(predicate, $pos) {
33975
35157
  function getCellsInColumn(columnIndex, selection) {
33976
35158
  const table = findTable(selection.$from);
33977
35159
  if (!table) return;
33978
- const map = TableMap2.get(table.node);
35160
+ const map = TableMap3.get(table.node);
33979
35161
  if (columnIndex < 0 || columnIndex > map.width - 1) return;
33980
35162
  return map.cellsInRect({
33981
35163
  left: columnIndex,
@@ -33996,7 +35178,7 @@ function getCellsInColumn(columnIndex, selection) {
33996
35178
  function getCellsInRow(rowIndex, selection) {
33997
35179
  const table = findTable(selection.$from);
33998
35180
  if (!table) return;
33999
- const map = TableMap2.get(table.node);
35181
+ const map = TableMap3.get(table.node);
34000
35182
  if (rowIndex < 0 || rowIndex > map.height - 1) return;
34001
35183
  return map.cellsInRect({
34002
35184
  left: 0,
@@ -34125,7 +35307,7 @@ function moveColumn(moveColParams) {
34125
35307
  const newTable = moveTableColumn$1(table.node, indexesOriginColumn, indexesTargetColumn, 0);
34126
35308
  tr.replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
34127
35309
  if (!select) return true;
34128
- const map = TableMap2.get(newTable);
35310
+ const map = TableMap3.get(newTable);
34129
35311
  const start = table.start;
34130
35312
  const index = targetIndex;
34131
35313
  const lastCell = map.positionAt(map.height - 1, index, newTable);
@@ -34153,7 +35335,7 @@ function moveRow(moveRowParams) {
34153
35335
  const newTable = moveTableRow$1(table.node, indexesOriginRow, indexesTargetRow, 0);
34154
35336
  tr.replaceWith(table.pos, table.pos + table.node.nodeSize, newTable);
34155
35337
  if (!select) return true;
34156
- const map = TableMap2.get(newTable);
35338
+ const map = TableMap3.get(newTable);
34157
35339
  const start = table.start;
34158
35340
  const index = targetIndex;
34159
35341
  const lastCell = map.positionAt(index, map.width - 1, newTable);
@@ -34168,12 +35350,12 @@ function moveTableRow$1(table, indexesOrigin, indexesTarget, direction) {
34168
35350
  rows = moveRowInArrayOfRows(rows, indexesOrigin, indexesTarget, direction);
34169
35351
  return convertArrayOfRowsToTableNode(table, rows);
34170
35352
  }
34171
- function selectedRect2(state) {
35353
+ function selectedRect3(state) {
34172
35354
  const sel = state.selection;
34173
35355
  const $pos = selectionCell(state);
34174
35356
  const table = $pos.node(-1);
34175
35357
  const tableStart = $pos.start(-1);
34176
- const map = TableMap2.get(table);
35358
+ const map = TableMap3.get(table);
34177
35359
  return {
34178
35360
  ...sel instanceof CellSelection ? map.rectBetween(sel.$anchorCell.pos - tableStart, sel.$headCell.pos - tableStart) : map.findCell($pos.pos - tableStart),
34179
35361
  tableStart,
@@ -34186,7 +35368,7 @@ function deprecated_toggleHeader(type) {
34186
35368
  if (!isInTable(state)) return false;
34187
35369
  if (dispatch) {
34188
35370
  const types = tableNodeTypes(state.schema);
34189
- const rect = selectedRect2(state), tr = state.tr;
35371
+ const rect = selectedRect3(state), tr = state.tr;
34190
35372
  const cells = rect.map.cellsInRect(type == "column" ? {
34191
35373
  left: rect.left,
34192
35374
  top: 0,
@@ -34226,7 +35408,7 @@ function toggleHeader(type, options) {
34226
35408
  if (!isInTable(state)) return false;
34227
35409
  if (dispatch) {
34228
35410
  const types = tableNodeTypes(state.schema);
34229
- const rect = selectedRect2(state), tr = state.tr;
35411
+ const rect = selectedRect3(state), tr = state.tr;
34230
35412
  const isHeaderRowEnabled = isHeaderEnabledByType("row", rect, types);
34231
35413
  const isHeaderColumnEnabled = isHeaderEnabledByType("column", rect, types);
34232
35414
  const selectionStartsAt = (type === "column" ? isHeaderRowEnabled : type === "row" ? isHeaderColumnEnabled : false) ? 1 : 0;
@@ -34387,6 +35569,262 @@ import {
34387
35569
  Trash2 as Trash24
34388
35570
  } from "lucide-react";
34389
35571
 
35572
+ // src/components/UEditor/table-layout-model.ts
35573
+ var FALLBACK_TABLE_ROW_HEIGHT = 44;
35574
+ var FALLBACK_TABLE_COLUMN_WIDTH = 160;
35575
+ function getVisibleTableBounds(layout) {
35576
+ const left = Math.max(layout.tableLeft, layout.wrapperLeft);
35577
+ const top = Math.max(layout.tableTop, layout.wrapperTop);
35578
+ const right = Math.min(layout.tableLeft + layout.tableWidth, layout.wrapperLeft + layout.viewportWidth);
35579
+ const bottom = Math.min(layout.tableTop + layout.tableHeight, layout.wrapperTop + layout.viewportHeight);
35580
+ return {
35581
+ left,
35582
+ top,
35583
+ right,
35584
+ bottom,
35585
+ width: Math.max(0, right - left),
35586
+ height: Math.max(0, bottom - top)
35587
+ };
35588
+ }
35589
+ function metricOrFallback(value, fallback) {
35590
+ return Number.isFinite(value) && value > 0 ? value : fallback;
35591
+ }
35592
+ function parsePixelMetric(value) {
35593
+ if (!value) return null;
35594
+ const parsed = Number.parseFloat(value);
35595
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
35596
+ }
35597
+ function getPrimaryCell(table) {
35598
+ const cell = table.querySelector("th,td");
35599
+ return cell instanceof HTMLTableCellElement ? cell : null;
35600
+ }
35601
+ function getLastCell(table) {
35602
+ const lastRow = table.rows.item(table.rows.length - 1);
35603
+ if (!(lastRow instanceof HTMLTableRowElement)) return null;
35604
+ const cell = lastRow.cells.item(lastRow.cells.length - 1);
35605
+ return cell instanceof HTMLTableCellElement ? cell : null;
35606
+ }
35607
+ function getCellFromTarget(target) {
35608
+ const element = resolveEventElement(target);
35609
+ if (!element) return null;
35610
+ const directCell = element.closest("th,td");
35611
+ if (directCell instanceof HTMLTableCellElement) {
35612
+ return directCell;
35613
+ }
35614
+ const table = element.closest("table");
35615
+ if (table instanceof HTMLTableElement) {
35616
+ return getPrimaryCell(table);
35617
+ }
35618
+ return null;
35619
+ }
35620
+ function findTableInfo(editor, pos) {
35621
+ const $pos = editor.state.doc.resolve(pos);
35622
+ for (let depth = $pos.depth; depth > 0; depth -= 1) {
35623
+ const node = $pos.node(depth);
35624
+ if (node.type.name === "table") {
35625
+ return {
35626
+ node,
35627
+ pos: $pos.before(depth),
35628
+ start: $pos.start(depth)
35629
+ };
35630
+ }
35631
+ }
35632
+ return null;
35633
+ }
35634
+ function getCellRelativePosFromDomPos(map, tableStart, domPos) {
35635
+ const relativeDomPos = domPos - tableStart;
35636
+ const seen = /* @__PURE__ */ new Set();
35637
+ for (const relativeCellPos of map.map) {
35638
+ if (seen.has(relativeCellPos)) continue;
35639
+ seen.add(relativeCellPos);
35640
+ if (relativeDomPos === relativeCellPos || relativeDomPos === relativeCellPos + 1) {
35641
+ return relativeCellPos;
35642
+ }
35643
+ }
35644
+ return null;
35645
+ }
35646
+ function buildLogicalColumnMetrics({
35647
+ editor,
35648
+ surface,
35649
+ surfaceRect,
35650
+ tableElement,
35651
+ tableInfo,
35652
+ tableLeft,
35653
+ tableWidth
35654
+ }) {
35655
+ const map = TableMap3.get(tableInfo.node);
35656
+ const fallbackWidth = metricOrFallback(tableWidth / map.width, FALLBACK_TABLE_COLUMN_WIDTH);
35657
+ const firstRow = tableElement.rows.item(0);
35658
+ const visualColumns = [];
35659
+ if (firstRow) {
35660
+ for (const tableCell of Array.from(firstRow.cells)) {
35661
+ if (!(tableCell instanceof HTMLTableCellElement)) continue;
35662
+ const cellPos = editor.view.posAtDOM(tableCell, 0);
35663
+ const relativeCellPos = getCellRelativePosFromDomPos(map, tableInfo.start, cellPos);
35664
+ if (relativeCellPos == null) continue;
35665
+ const cellMapRect = map.findCell(relativeCellPos);
35666
+ const cellRect = tableCell.getBoundingClientRect();
35667
+ const cellStart = cellRect.width > 0 ? cellRect.left - surfaceRect.left + surface.scrollLeft : tableLeft + cellMapRect.left * fallbackWidth;
35668
+ const size = metricOrFallback(cellRect.width, fallbackWidth * Math.max(1, cellMapRect.right - cellMapRect.left));
35669
+ visualColumns.push({
35670
+ index: cellMapRect.left,
35671
+ cellPos: tableInfo.start + relativeCellPos,
35672
+ start: cellStart,
35673
+ size,
35674
+ center: cellStart + size / 2
35675
+ });
35676
+ }
35677
+ }
35678
+ if (visualColumns.length > 0) {
35679
+ return visualColumns.sort((a, b) => a.index - b.index);
35680
+ }
35681
+ const cols = Array.from(tableElement.querySelectorAll("colgroup > col"));
35682
+ const parsedWidths = cols.slice(0, map.width).map((col) => parsePixelMetric(col.style.width) ?? parsePixelMetric(col.getAttribute("width")));
35683
+ const hasCompleteColWidths = parsedWidths.length >= map.width && parsedWidths.every((width) => typeof width === "number");
35684
+ let cursor = tableLeft;
35685
+ return Array.from({ length: map.width }, (_, index) => {
35686
+ const size = hasCompleteColWidths ? parsedWidths[index] : fallbackWidth;
35687
+ const start = hasCompleteColWidths ? cursor : tableLeft + index * fallbackWidth;
35688
+ cursor += size;
35689
+ return {
35690
+ index,
35691
+ cellPos: tableInfo.start + map.positionAt(0, index, tableInfo.node),
35692
+ start,
35693
+ size,
35694
+ center: start + size / 2
35695
+ };
35696
+ });
35697
+ }
35698
+ function buildLogicalRowMetrics({
35699
+ editor,
35700
+ surface,
35701
+ surfaceRect,
35702
+ tableInfo,
35703
+ rows,
35704
+ tableTop,
35705
+ tableHeight,
35706
+ cornerCell
35707
+ }) {
35708
+ const map = TableMap3.get(tableInfo.node);
35709
+ const fallbackHeight = metricOrFallback(tableHeight / map.height, FALLBACK_TABLE_ROW_HEIGHT);
35710
+ const visualRows = [];
35711
+ const seenCellPositions = /* @__PURE__ */ new Set();
35712
+ for (let rowIndex = 0; rowIndex < map.height; rowIndex += 1) {
35713
+ const relativeCellPos = map.map[rowIndex * map.width];
35714
+ if (seenCellPositions.has(relativeCellPos)) continue;
35715
+ seenCellPositions.add(relativeCellPos);
35716
+ const cellMapRect = map.findCell(relativeCellPos);
35717
+ const cellDom = editor.view.nodeDOM(tableInfo.start + relativeCellPos);
35718
+ const tableCell = cellDom instanceof HTMLTableCellElement ? cellDom : null;
35719
+ if (tableCell) {
35720
+ const cellRect = tableCell.getBoundingClientRect();
35721
+ const start = cellRect.height > 0 ? cellRect.top - surfaceRect.top + surface.scrollTop : tableTop + cellMapRect.top * fallbackHeight;
35722
+ const size = metricOrFallback(cellRect.height, fallbackHeight * Math.max(1, cellMapRect.bottom - cellMapRect.top));
35723
+ visualRows.push({
35724
+ index: cellMapRect.top,
35725
+ cellPos: tableInfo.start + relativeCellPos,
35726
+ start,
35727
+ size,
35728
+ center: start + size / 2
35729
+ });
35730
+ }
35731
+ }
35732
+ if (visualRows.length > 0) {
35733
+ return visualRows.sort((a, b) => a.index - b.index);
35734
+ }
35735
+ return rows.map((tableRow, index) => {
35736
+ const rowRect = tableRow.getBoundingClientRect();
35737
+ const anchorCell = tableRow.cells.item(0) ?? cornerCell;
35738
+ const start = rowRect.height > 0 ? rowRect.top - surfaceRect.top + surface.scrollTop : tableTop + index * fallbackHeight;
35739
+ const size = metricOrFallback(rowRect.height, fallbackHeight);
35740
+ return {
35741
+ index,
35742
+ cellPos: editor.view.posAtDOM(anchorCell, 0),
35743
+ start,
35744
+ size,
35745
+ center: start + size / 2
35746
+ };
35747
+ });
35748
+ }
35749
+ function buildTableControlLayout(editor, surface, cell) {
35750
+ const row = cell.closest("tr");
35751
+ const table = cell.closest("table");
35752
+ if (!(row instanceof HTMLTableRowElement) || !(table instanceof HTMLTableElement)) {
35753
+ return null;
35754
+ }
35755
+ const rows = Array.from(table.rows).filter((item) => item instanceof HTMLTableRowElement);
35756
+ const cornerCell = getLastCell(table);
35757
+ const cellPos = editor.view.posAtDOM(cell, 0);
35758
+ const tableInfo = findTableInfo(editor, cellPos);
35759
+ if (rows.length === 0 || !tableInfo || !(cornerCell instanceof HTMLTableCellElement)) {
35760
+ return null;
35761
+ }
35762
+ const map = TableMap3.get(tableInfo.node);
35763
+ const surfaceRect = surface.getBoundingClientRect();
35764
+ const tableRect = table.getBoundingClientRect();
35765
+ const wrapperElement = table.closest(".tableWrapper");
35766
+ const wrapper = wrapperElement instanceof HTMLElement ? wrapperElement : null;
35767
+ const wrapperRect = wrapper?.getBoundingClientRect() ?? tableRect;
35768
+ const tableLeft = tableRect.left - surfaceRect.left + surface.scrollLeft;
35769
+ const tableTop = tableRect.top - surfaceRect.top + surface.scrollTop;
35770
+ const avgRowHeight = metricOrFallback(tableRect.height / rows.length, FALLBACK_TABLE_ROW_HEIGHT);
35771
+ const avgColumnWidth = metricOrFallback(tableRect.width / map.width, FALLBACK_TABLE_COLUMN_WIDTH);
35772
+ const tableWidth = metricOrFallback(tableRect.width, avgColumnWidth * map.width);
35773
+ const tableHeight = metricOrFallback(tableRect.height, avgRowHeight * rows.length);
35774
+ const wrapperLeft = wrapperRect.left - surfaceRect.left + surface.scrollLeft;
35775
+ const wrapperTop = wrapperRect.top - surfaceRect.top + surface.scrollTop;
35776
+ const wrapperWidth = metricOrFallback(wrapperRect.width, tableWidth);
35777
+ const wrapperHeight = metricOrFallback(wrapperRect.height, tableHeight);
35778
+ const viewportWidth = metricOrFallback(wrapper?.clientWidth ?? wrapperRect.width, tableWidth);
35779
+ const viewportHeight = metricOrFallback(wrapper?.clientHeight ?? wrapperRect.height, tableHeight);
35780
+ const verticalScrollbarWidth = Math.max(0, Math.round(wrapperWidth - viewportWidth));
35781
+ const horizontalScrollbarHeight = Math.max(0, Math.round(wrapperHeight - viewportHeight));
35782
+ const rowHandles = buildLogicalRowMetrics({
35783
+ editor,
35784
+ surface,
35785
+ surfaceRect,
35786
+ tableInfo,
35787
+ rows,
35788
+ tableTop,
35789
+ tableHeight,
35790
+ cornerCell
35791
+ });
35792
+ const columnHandles = buildLogicalColumnMetrics({
35793
+ editor,
35794
+ surface,
35795
+ surfaceRect,
35796
+ tableElement: table,
35797
+ tableInfo,
35798
+ tableLeft,
35799
+ tableWidth
35800
+ });
35801
+ const activeCellRelativePos = getCellRelativePosFromDomPos(map, tableInfo.start, cellPos);
35802
+ const activeCellRect = activeCellRelativePos != null ? map.findCell(activeCellRelativePos) : { left: cell.cellIndex, top: row.rowIndex };
35803
+ const normalizedCellPos = activeCellRelativePos != null ? tableInfo.start + activeCellRelativePos : cellPos;
35804
+ return {
35805
+ cellPos: normalizedCellPos,
35806
+ cornerCellPos: tableInfo.start + map.positionAt(map.height - 1, map.width - 1, tableInfo.node),
35807
+ activeRowIndex: activeCellRect.top,
35808
+ activeColumnIndex: activeCellRect.left,
35809
+ tableLeft,
35810
+ tableTop,
35811
+ tableWidth,
35812
+ tableHeight,
35813
+ wrapperLeft,
35814
+ wrapperTop,
35815
+ wrapperWidth,
35816
+ wrapperHeight,
35817
+ viewportWidth,
35818
+ viewportHeight,
35819
+ horizontalScrollbarHeight,
35820
+ verticalScrollbarWidth,
35821
+ avgRowHeight,
35822
+ avgColumnWidth,
35823
+ rowHandles,
35824
+ columnHandles
35825
+ };
35826
+ }
35827
+
34390
35828
  // src/components/UEditor/table-hover-state.ts
34391
35829
  var MENU_HOVER_PADDING = 18;
34392
35830
  var ROW_HANDLE_HOVER_WIDTH = 28;
@@ -34420,18 +35858,18 @@ function buildTableHoverState({
34420
35858
  const directAddRow = targetElement?.closest?.("[data-table-control='add-row']");
34421
35859
  const directRowHandleIndex = directRowHandle instanceof HTMLElement ? Number.parseInt(directRowHandle.dataset.rowHandleIndex ?? "", 10) : Number.NaN;
34422
35860
  const directColumnHandleIndex = directColumnHandle instanceof HTMLElement ? Number.parseInt(directColumnHandle.dataset.columnHandleIndex ?? "", 10) : Number.NaN;
34423
- const visibleTableWidth = Math.min(layout.tableWidth, layout.viewportWidth);
34424
- const visibleTableHeight = Math.min(layout.tableHeight, layout.viewportHeight);
35861
+ const visibleBounds = getVisibleTableBounds(layout);
35862
+ const rowRailTop = layout.wrapperTop + layout.wrapperHeight;
34425
35863
  const isMouseInTable = relativeX >= layout.tableLeft && relativeX <= layout.tableLeft + layout.tableWidth && relativeY >= layout.tableTop && relativeY <= layout.tableTop + layout.tableHeight;
34426
35864
  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;
34427
35865
  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;
34428
35866
  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;
34429
35867
  const lastRow = layout.rowHandles[layout.rowHandles.length - 1];
34430
35868
  const lastCol = layout.columnHandles[layout.columnHandles.length - 1];
34431
- const isMouseInLastColumn = lastCol ? relativeX >= lastCol.start && relativeX <= lastCol.start + lastCol.size && relativeY >= layout.tableTop && relativeY <= layout.tableTop + visibleTableHeight : false;
34432
- const addColumnVisible = Boolean(directAddColumn) || relativeX >= layout.tableLeft + visibleTableWidth && relativeX <= layout.tableLeft + visibleTableWidth + ADD_COLUMN_HOVER_WIDTH && relativeY >= layout.tableTop && relativeY <= layout.tableTop + visibleTableHeight || isMouseInLastColumn;
34433
- const isMouseInLastRow = lastRow ? relativeY >= lastRow.start && relativeY <= lastRow.start + lastRow.size && relativeX >= layout.tableLeft && relativeX <= layout.tableLeft + visibleTableWidth : false;
34434
- const addRowVisible = Boolean(directAddRow) || relativeY >= layout.tableTop + visibleTableHeight && relativeY <= layout.tableTop + visibleTableHeight + ADD_ROW_HOVER_HEIGHT && relativeX >= layout.tableLeft && relativeX <= layout.tableLeft + visibleTableWidth || isMouseInLastRow;
35869
+ const isMouseInLastColumn = lastCol ? relativeX >= lastCol.start && relativeX <= lastCol.start + lastCol.size && relativeY >= visibleBounds.top && relativeY <= visibleBounds.bottom : false;
35870
+ const addColumnVisible = Boolean(directAddColumn) || relativeX >= visibleBounds.right && relativeX <= visibleBounds.right + ADD_COLUMN_HOVER_WIDTH && relativeY >= visibleBounds.top && relativeY <= visibleBounds.bottom || isMouseInLastColumn;
35871
+ const isMouseInLastRow = lastRow ? relativeY >= lastRow.start && relativeY <= lastRow.start + lastRow.size && relativeX >= visibleBounds.left && relativeX <= visibleBounds.right : false;
35872
+ const addRowVisible = Boolean(directAddRow) || relativeY >= rowRailTop && relativeY <= rowRailTop + ADD_ROW_HOVER_HEIGHT && relativeX >= visibleBounds.left && relativeX <= visibleBounds.right || isMouseInLastRow;
34435
35873
  return {
34436
35874
  menuVisible,
34437
35875
  addColumnVisible,
@@ -34553,12 +35991,11 @@ function TableAddRails({
34553
35991
  quickAddColumnLabel,
34554
35992
  quickAddRowLabel
34555
35993
  }) {
34556
- const visibleTableWidth = Math.min(layout.tableWidth, layout.viewportWidth);
34557
- const visibleTableHeight = Math.min(layout.tableHeight, layout.viewportHeight);
34558
- const columnRailTop = layout.tableTop;
34559
- const columnRailLeft = layout.tableLeft + visibleTableWidth + ADD_COLUMN_RAIL_GAP;
35994
+ const visibleBounds = getVisibleTableBounds(layout);
35995
+ const columnRailTop = visibleBounds.top;
35996
+ const columnRailLeft = visibleBounds.right + ADD_COLUMN_RAIL_GAP;
34560
35997
  const rowRailTop = layout.wrapperTop + layout.wrapperHeight + ADD_ROW_RAIL_GAP;
34561
- const rowRailLeft = Math.max(layout.tableLeft, layout.wrapperLeft);
35998
+ const rowRailLeft = visibleBounds.left;
34562
35999
  const showColumnRail = controlsVisible || addColumnVisible;
34563
36000
  const showRowRail = controlsVisible || addRowVisible;
34564
36001
  return /* @__PURE__ */ jsxs75(Fragment32, { children: [
@@ -34586,10 +36023,10 @@ function TableAddRails({
34586
36023
  "transition-[opacity,transform,colors] duration-150 hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed"
34587
36024
  ),
34588
36025
  style: {
34589
- top: showColumnRail ? columnRailTop : columnRailTop + Math.max(0, visibleTableHeight / 2 - 24),
36026
+ top: showColumnRail ? columnRailTop : columnRailTop + Math.max(0, visibleBounds.height / 2 - 24),
34590
36027
  left: columnRailLeft,
34591
36028
  width: showColumnRail ? 18 : 12,
34592
- height: showColumnRail ? visibleTableHeight : 48,
36029
+ height: showColumnRail ? visibleBounds.height : 48,
34593
36030
  opacity: showColumnRail ? 1 : 0,
34594
36031
  transform: showColumnRail ? "scale(1)" : "scale(0.92)",
34595
36032
  pointerEvents: showColumnRail ? "auto" : "none"
@@ -34624,8 +36061,8 @@ function TableAddRails({
34624
36061
  ),
34625
36062
  style: {
34626
36063
  top: rowRailTop,
34627
- left: showRowRail ? rowRailLeft : rowRailLeft + Math.max(0, visibleTableWidth / 2 - 24),
34628
- width: showRowRail ? visibleTableWidth : 48,
36064
+ left: showRowRail ? rowRailLeft : rowRailLeft + Math.max(0, visibleBounds.width / 2 - 24),
36065
+ width: showRowRail ? visibleBounds.width : 48,
34629
36066
  height: showRowRail ? 16 : 12,
34630
36067
  opacity: showRowRail ? 1 : 0,
34631
36068
  transform: showRowRail ? "scale(1)" : "scale(0.92)",
@@ -34850,248 +36287,6 @@ function TableColumnHandles({
34850
36287
  }) });
34851
36288
  }
34852
36289
 
34853
- // src/components/UEditor/table-layout-model.ts
34854
- var FALLBACK_TABLE_ROW_HEIGHT = 44;
34855
- var FALLBACK_TABLE_COLUMN_WIDTH = 160;
34856
- function metricOrFallback(value, fallback) {
34857
- return Number.isFinite(value) && value > 0 ? value : fallback;
34858
- }
34859
- function parsePixelMetric(value) {
34860
- if (!value) return null;
34861
- const parsed = Number.parseFloat(value);
34862
- return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
34863
- }
34864
- function getPrimaryCell(table) {
34865
- const cell = table.querySelector("th,td");
34866
- return cell instanceof HTMLTableCellElement ? cell : null;
34867
- }
34868
- function getLastCell(table) {
34869
- const lastRow = table.rows.item(table.rows.length - 1);
34870
- if (!(lastRow instanceof HTMLTableRowElement)) return null;
34871
- const cell = lastRow.cells.item(lastRow.cells.length - 1);
34872
- return cell instanceof HTMLTableCellElement ? cell : null;
34873
- }
34874
- function getCellFromTarget(target) {
34875
- const element = resolveEventElement(target);
34876
- if (!element) return null;
34877
- const directCell = element.closest("th,td");
34878
- if (directCell instanceof HTMLTableCellElement) {
34879
- return directCell;
34880
- }
34881
- const table = element.closest("table");
34882
- if (table instanceof HTMLTableElement) {
34883
- return getPrimaryCell(table);
34884
- }
34885
- return null;
34886
- }
34887
- function findTableInfo(editor, pos) {
34888
- const $pos = editor.state.doc.resolve(pos);
34889
- for (let depth = $pos.depth; depth > 0; depth -= 1) {
34890
- const node = $pos.node(depth);
34891
- if (node.type.name === "table") {
34892
- return {
34893
- node,
34894
- pos: $pos.before(depth),
34895
- start: $pos.start(depth)
34896
- };
34897
- }
34898
- }
34899
- return null;
34900
- }
34901
- function getCellRelativePosFromDomPos(map, tableStart, domPos) {
34902
- const relativeDomPos = domPos - tableStart;
34903
- const seen = /* @__PURE__ */ new Set();
34904
- for (const relativeCellPos of map.map) {
34905
- if (seen.has(relativeCellPos)) continue;
34906
- seen.add(relativeCellPos);
34907
- if (relativeDomPos === relativeCellPos || relativeDomPos === relativeCellPos + 1) {
34908
- return relativeCellPos;
34909
- }
34910
- }
34911
- return null;
34912
- }
34913
- function buildLogicalColumnMetrics({
34914
- editor,
34915
- surface,
34916
- surfaceRect,
34917
- tableElement,
34918
- tableInfo,
34919
- tableLeft,
34920
- tableWidth
34921
- }) {
34922
- const map = TableMap2.get(tableInfo.node);
34923
- const fallbackWidth = metricOrFallback(tableWidth / map.width, FALLBACK_TABLE_COLUMN_WIDTH);
34924
- const firstRow = tableElement.rows.item(0);
34925
- const visualColumns = [];
34926
- if (firstRow) {
34927
- for (const tableCell of Array.from(firstRow.cells)) {
34928
- if (!(tableCell instanceof HTMLTableCellElement)) continue;
34929
- const cellPos = editor.view.posAtDOM(tableCell, 0);
34930
- const relativeCellPos = getCellRelativePosFromDomPos(map, tableInfo.start, cellPos);
34931
- if (relativeCellPos == null) continue;
34932
- const cellMapRect = map.findCell(relativeCellPos);
34933
- const cellRect = tableCell.getBoundingClientRect();
34934
- const cellStart = cellRect.width > 0 ? cellRect.left - surfaceRect.left + surface.scrollLeft : tableLeft + cellMapRect.left * fallbackWidth;
34935
- const size = metricOrFallback(cellRect.width, fallbackWidth * Math.max(1, cellMapRect.right - cellMapRect.left));
34936
- visualColumns.push({
34937
- index: cellMapRect.left,
34938
- cellPos: tableInfo.start + relativeCellPos,
34939
- start: cellStart,
34940
- size,
34941
- center: cellStart + size / 2
34942
- });
34943
- }
34944
- }
34945
- if (visualColumns.length > 0) {
34946
- return visualColumns.sort((a, b) => a.index - b.index);
34947
- }
34948
- const cols = Array.from(tableElement.querySelectorAll("colgroup > col"));
34949
- const parsedWidths = cols.slice(0, map.width).map((col) => parsePixelMetric(col.style.width) ?? parsePixelMetric(col.getAttribute("width")));
34950
- const hasCompleteColWidths = parsedWidths.length >= map.width && parsedWidths.every((width) => typeof width === "number");
34951
- let cursor = tableLeft;
34952
- return Array.from({ length: map.width }, (_, index) => {
34953
- const size = hasCompleteColWidths ? parsedWidths[index] : fallbackWidth;
34954
- const start = hasCompleteColWidths ? cursor : tableLeft + index * fallbackWidth;
34955
- cursor += size;
34956
- return {
34957
- index,
34958
- cellPos: tableInfo.start + map.positionAt(0, index, tableInfo.node),
34959
- start,
34960
- size,
34961
- center: start + size / 2
34962
- };
34963
- });
34964
- }
34965
- function buildLogicalRowMetrics({
34966
- editor,
34967
- surface,
34968
- surfaceRect,
34969
- tableInfo,
34970
- rows,
34971
- tableTop,
34972
- tableHeight,
34973
- cornerCell
34974
- }) {
34975
- const map = TableMap2.get(tableInfo.node);
34976
- const fallbackHeight = metricOrFallback(tableHeight / map.height, FALLBACK_TABLE_ROW_HEIGHT);
34977
- const visualRows = [];
34978
- const seenCellPositions = /* @__PURE__ */ new Set();
34979
- for (let rowIndex = 0; rowIndex < map.height; rowIndex += 1) {
34980
- const relativeCellPos = map.map[rowIndex * map.width];
34981
- if (seenCellPositions.has(relativeCellPos)) continue;
34982
- seenCellPositions.add(relativeCellPos);
34983
- const cellMapRect = map.findCell(relativeCellPos);
34984
- const cellDom = editor.view.nodeDOM(tableInfo.start + relativeCellPos);
34985
- const tableCell = cellDom instanceof HTMLTableCellElement ? cellDom : null;
34986
- if (tableCell) {
34987
- const cellRect = tableCell.getBoundingClientRect();
34988
- const start = cellRect.height > 0 ? cellRect.top - surfaceRect.top + surface.scrollTop : tableTop + cellMapRect.top * fallbackHeight;
34989
- const size = metricOrFallback(cellRect.height, fallbackHeight * Math.max(1, cellMapRect.bottom - cellMapRect.top));
34990
- visualRows.push({
34991
- index: cellMapRect.top,
34992
- cellPos: tableInfo.start + relativeCellPos,
34993
- start,
34994
- size,
34995
- center: start + size / 2
34996
- });
34997
- }
34998
- }
34999
- if (visualRows.length > 0) {
35000
- return visualRows.sort((a, b) => a.index - b.index);
35001
- }
35002
- return rows.map((tableRow, index) => {
35003
- const rowRect = tableRow.getBoundingClientRect();
35004
- const anchorCell = tableRow.cells.item(0) ?? cornerCell;
35005
- const start = rowRect.height > 0 ? rowRect.top - surfaceRect.top + surface.scrollTop : tableTop + index * fallbackHeight;
35006
- const size = metricOrFallback(rowRect.height, fallbackHeight);
35007
- return {
35008
- index,
35009
- cellPos: editor.view.posAtDOM(anchorCell, 0),
35010
- start,
35011
- size,
35012
- center: start + size / 2
35013
- };
35014
- });
35015
- }
35016
- function buildTableControlLayout(editor, surface, cell) {
35017
- const row = cell.closest("tr");
35018
- const table = cell.closest("table");
35019
- if (!(row instanceof HTMLTableRowElement) || !(table instanceof HTMLTableElement)) {
35020
- return null;
35021
- }
35022
- const rows = Array.from(table.rows).filter((item) => item instanceof HTMLTableRowElement);
35023
- const cornerCell = getLastCell(table);
35024
- const cellPos = editor.view.posAtDOM(cell, 0);
35025
- const tableInfo = findTableInfo(editor, cellPos);
35026
- if (rows.length === 0 || !tableInfo || !(cornerCell instanceof HTMLTableCellElement)) {
35027
- return null;
35028
- }
35029
- const map = TableMap2.get(tableInfo.node);
35030
- const surfaceRect = surface.getBoundingClientRect();
35031
- const tableRect = table.getBoundingClientRect();
35032
- const wrapperElement = table.closest(".tableWrapper");
35033
- const wrapper = wrapperElement instanceof HTMLElement ? wrapperElement : null;
35034
- const wrapperRect = wrapper?.getBoundingClientRect() ?? tableRect;
35035
- const tableLeft = tableRect.left - surfaceRect.left + surface.scrollLeft;
35036
- const tableTop = tableRect.top - surfaceRect.top + surface.scrollTop;
35037
- const avgRowHeight = metricOrFallback(tableRect.height / rows.length, FALLBACK_TABLE_ROW_HEIGHT);
35038
- const avgColumnWidth = metricOrFallback(tableRect.width / map.width, FALLBACK_TABLE_COLUMN_WIDTH);
35039
- const tableWidth = metricOrFallback(tableRect.width, avgColumnWidth * map.width);
35040
- const tableHeight = metricOrFallback(tableRect.height, avgRowHeight * rows.length);
35041
- const wrapperLeft = wrapperRect.left - surfaceRect.left + surface.scrollLeft;
35042
- const wrapperTop = wrapperRect.top - surfaceRect.top + surface.scrollTop;
35043
- const wrapperWidth = metricOrFallback(wrapperRect.width, tableWidth);
35044
- const wrapperHeight = metricOrFallback(wrapperRect.height, tableHeight);
35045
- const viewportWidth = metricOrFallback(wrapper?.clientWidth ?? wrapperRect.width, tableWidth);
35046
- const viewportHeight = metricOrFallback(wrapper?.clientHeight ?? wrapperRect.height, tableHeight);
35047
- const verticalScrollbarWidth = Math.max(0, Math.round(wrapperWidth - viewportWidth));
35048
- const horizontalScrollbarHeight = Math.max(0, Math.round(wrapperHeight - viewportHeight));
35049
- const rowHandles = buildLogicalRowMetrics({
35050
- editor,
35051
- surface,
35052
- surfaceRect,
35053
- tableInfo,
35054
- rows,
35055
- tableTop,
35056
- tableHeight,
35057
- cornerCell
35058
- });
35059
- const columnHandles = buildLogicalColumnMetrics({
35060
- editor,
35061
- surface,
35062
- surfaceRect,
35063
- tableElement: table,
35064
- tableInfo,
35065
- tableLeft,
35066
- tableWidth
35067
- });
35068
- const activeCellRelativePos = getCellRelativePosFromDomPos(map, tableInfo.start, cellPos);
35069
- const activeCellRect = activeCellRelativePos != null ? map.findCell(activeCellRelativePos) : { left: cell.cellIndex, top: row.rowIndex };
35070
- const normalizedCellPos = activeCellRelativePos != null ? tableInfo.start + activeCellRelativePos : cellPos;
35071
- return {
35072
- cellPos: normalizedCellPos,
35073
- cornerCellPos: tableInfo.start + map.positionAt(map.height - 1, map.width - 1, tableInfo.node),
35074
- activeRowIndex: activeCellRect.top,
35075
- activeColumnIndex: activeCellRect.left,
35076
- tableLeft,
35077
- tableTop,
35078
- tableWidth,
35079
- tableHeight,
35080
- wrapperLeft,
35081
- wrapperTop,
35082
- wrapperWidth,
35083
- wrapperHeight,
35084
- viewportWidth,
35085
- viewportHeight,
35086
- horizontalScrollbarHeight,
35087
- verticalScrollbarWidth,
35088
- avgRowHeight,
35089
- avgColumnWidth,
35090
- rowHandles,
35091
- columnHandles
35092
- };
35093
- }
35094
-
35095
36290
  // src/components/UEditor/table-controls.tsx
35096
36291
  import { Fragment as Fragment34, jsx as jsx91, jsxs as jsxs76 } from "react/jsx-runtime";
35097
36292
  var TABLE_MENU_TOP_OFFSET = 10;