@underverse-ui/underverse 2.0.28 → 2.0.30

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.
Files changed (35) hide show
  1. package/api-reference.json +1 -1
  2. package/dist/{EmojiPicker-NV3GQYWI.js → EmojiPicker-ISJ6RXYG.js} +4 -4
  3. package/dist/{chunk-Y4X6CAZY.js → chunk-2P7TSXEE.js} +82 -25
  4. package/dist/chunk-2P7TSXEE.js.map +1 -0
  5. package/dist/{chunk-KTOXCD6J.js → chunk-37MHVJMV.js} +1078 -90
  6. package/dist/chunk-37MHVJMV.js.map +1 -0
  7. package/dist/{chunk-QYFXYDBK.js → chunk-57BKIC6J.js} +1306 -888
  8. package/dist/chunk-57BKIC6J.js.map +1 -0
  9. package/dist/{chunk-LRIRY4DG.js → chunk-5W63IHIN.js} +11 -3
  10. package/dist/chunk-5W63IHIN.js.map +1 -0
  11. package/dist/{chunk-F7MX3XXK.js → chunk-S4UHFRL3.js} +3 -3
  12. package/dist/{chunk-A4GUPKHM.js → chunk-ZP7F4ZD7.js} +63 -43
  13. package/dist/chunk-ZP7F4ZD7.js.map +1 -0
  14. package/dist/index.cjs +3275 -1628
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +55 -0
  17. package/dist/index.d.ts +55 -0
  18. package/dist/index.js +23 -10
  19. package/dist/index.js.map +1 -1
  20. package/dist/{menu-bar-CFIWMBAA.js → menu-bar-DPCNNUX5.js} +227 -54
  21. package/dist/menu-bar-DPCNNUX5.js.map +1 -0
  22. package/dist/ueditor.cjs +9803 -8169
  23. package/dist/ueditor.cjs.map +1 -1
  24. package/dist/ueditor.d.cts +10 -0
  25. package/dist/ueditor.d.ts +10 -0
  26. package/dist/ueditor.js +4 -4
  27. package/package.json +2 -1
  28. package/dist/chunk-A4GUPKHM.js.map +0 -1
  29. package/dist/chunk-KTOXCD6J.js.map +0 -1
  30. package/dist/chunk-LRIRY4DG.js.map +0 -1
  31. package/dist/chunk-QYFXYDBK.js.map +0 -1
  32. package/dist/chunk-Y4X6CAZY.js.map +0 -1
  33. package/dist/menu-bar-CFIWMBAA.js.map +0 -1
  34. /package/dist/{EmojiPicker-NV3GQYWI.js.map → EmojiPicker-ISJ6RXYG.js.map} +0 -0
  35. /package/dist/{chunk-F7MX3XXK.js.map → chunk-S4UHFRL3.js.map} +0 -0
@@ -11,7 +11,7 @@ import {
11
11
  setRefValue,
12
12
  useSmartTranslations,
13
13
  useUEditorPortalContainerGetter
14
- } from "./chunk-A4GUPKHM.js";
14
+ } from "./chunk-ZP7F4ZD7.js";
15
15
 
16
16
  // src/contexts/UnderverseConfigContext.tsx
17
17
  import * as React from "react";
@@ -834,6 +834,8 @@ var DropdownMenu = ({
834
834
  } else if (e.key === "Escape" && (isInMenu || isOnTrigger)) {
835
835
  e.preventDefault();
836
836
  closeMenu();
837
+ const focusTarget = triggerEl.matches("button,a,input,select,textarea,[tabindex]") ? triggerEl : triggerEl.querySelector("button,a,input,select,textarea,[tabindex]");
838
+ focusTarget?.focus();
837
839
  }
838
840
  };
839
841
  const menuDocument = menuRef.current?.ownerDocument;
@@ -900,7 +902,7 @@ var DropdownMenu = ({
900
902
  item.destructive && "text-destructive hover:bg-destructive/10 focus:bg-destructive/10"
901
903
  ),
902
904
  children: [
903
- IconComponent && /* @__PURE__ */ jsx3(IconComponent, { className: "h-4 w-4" }),
905
+ IconComponent && /* @__PURE__ */ jsx3(IconComponent, { "aria-hidden": "true", className: "h-4 w-4" }),
904
906
  item.label
905
907
  ]
906
908
  },
@@ -928,11 +930,11 @@ var DropdownMenu = ({
928
930
  else callback(0);
929
931
  };
930
932
  if (!disabled) {
931
- if (event.key === "ArrowDown") {
933
+ if (!event.altKey && !event.ctrlKey && !event.metaKey && event.key === "ArrowDown") {
932
934
  event.preventDefault();
933
935
  setOpen(true);
934
936
  scheduleFrame(() => focusMenuItem(0));
935
- } else if (event.key === "ArrowUp") {
937
+ } else if (!event.altKey && !event.ctrlKey && !event.metaKey && event.key === "ArrowUp") {
936
938
  event.preventDefault();
937
939
  setOpen(true);
938
940
  scheduleFrame(() => {
@@ -1011,6 +1013,8 @@ var DropdownMenuItem = ({
1011
1013
  return /* @__PURE__ */ jsxs2(
1012
1014
  "button",
1013
1015
  {
1016
+ type: "button",
1017
+ role: "menuitem",
1014
1018
  onClick: () => {
1015
1019
  onClick?.();
1016
1020
  if (shouldCloseOnSelect) {
@@ -1032,19 +1036,19 @@ var DropdownMenuItem = ({
1032
1036
  className
1033
1037
  ),
1034
1038
  children: [
1035
- Icon && /* @__PURE__ */ jsx3(Icon, { className: cn("w-4 h-4 shrink-0", active ? "text-primary" : "opacity-60 group-hover:opacity-100") }),
1039
+ Icon && /* @__PURE__ */ jsx3(Icon, { "aria-hidden": "true", className: cn("w-4 h-4 shrink-0", active ? "text-primary" : "opacity-60 group-hover:opacity-100") }),
1036
1040
  /* @__PURE__ */ jsxs2("div", { className: "flex-1 text-left", children: [
1037
1041
  label && /* @__PURE__ */ jsx3("div", { className: cn("font-medium", description && "leading-tight"), children: label }),
1038
1042
  description && /* @__PURE__ */ jsx3("div", { className: "text-xs text-muted-foreground", children: description }),
1039
1043
  children
1040
1044
  ] }),
1041
1045
  shortcut && /* @__PURE__ */ jsx3("span", { className: "ml-2 text-xs text-muted-foreground opacity-60", children: shortcut }),
1042
- active && /* @__PURE__ */ jsx3("svg", { className: "w-4 h-4 text-primary shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx3("polyline", { points: "20 6 9 17 4 12" }) })
1046
+ active && /* @__PURE__ */ jsx3("svg", { "aria-hidden": "true", className: "w-4 h-4 text-primary shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx3("polyline", { points: "20 6 9 17 4 12" }) })
1043
1047
  ]
1044
1048
  }
1045
1049
  );
1046
1050
  };
1047
- var DropdownMenuSeparator = ({ className }) => /* @__PURE__ */ jsx3("div", { className: cn("h-px bg-border my-1", className) });
1051
+ var DropdownMenuSeparator = ({ className }) => /* @__PURE__ */ jsx3("div", { role: "separator", className: cn("h-px bg-border my-1", className) });
1048
1052
  var DropdownMenuSub = ({ label, icon: Icon, disabled, borderMode, getPortalContainer, children }) => {
1049
1053
  const globalConfig = useUnderverseUIConfig();
1050
1054
  const resolvedBorderMode = borderMode ?? globalConfig.dropdownMenu?.borderMode ?? globalConfig.borderMode;
@@ -1055,6 +1059,9 @@ var DropdownMenuSub = ({ label, icon: Icon, disabled, borderMode, getPortalConta
1055
1059
  "button",
1056
1060
  {
1057
1061
  type: "button",
1062
+ role: "menuitem",
1063
+ "data-dropdown-menu-item": "",
1064
+ tabIndex: -1,
1058
1065
  disabled,
1059
1066
  onMouseDown: (e) => e.preventDefault(),
1060
1067
  className: cn(
@@ -1065,9 +1072,9 @@ var DropdownMenuSub = ({ label, icon: Icon, disabled, borderMode, getPortalConta
1065
1072
  "disabled:opacity-50 disabled:cursor-not-allowed"
1066
1073
  ),
1067
1074
  children: [
1068
- Icon && /* @__PURE__ */ jsx3(Icon, { className: "w-4 h-4 shrink-0 opacity-60" }),
1075
+ Icon && /* @__PURE__ */ jsx3(Icon, { "aria-hidden": "true", className: "w-4 h-4 shrink-0 opacity-60" }),
1069
1076
  /* @__PURE__ */ jsx3("span", { className: "flex-1 text-left", children: label }),
1070
- /* @__PURE__ */ jsx3(ChevronRight, { className: "w-3 h-3 opacity-50" })
1077
+ /* @__PURE__ */ jsx3(ChevronRight, { "aria-hidden": "true", className: "w-3 h-3 opacity-50" })
1071
1078
  ]
1072
1079
  }
1073
1080
  ),
@@ -1170,6 +1177,7 @@ function isDataFileUrl(value) {
1170
1177
  function isSafeUEditorUrl(raw, kind) {
1171
1178
  const value = normalizeUrlInput(raw);
1172
1179
  if (!value) return false;
1180
+ if (/[<>'"`]/.test(value)) return false;
1173
1181
  if (kind === "image" && isDataImageUrl(value)) return true;
1174
1182
  if (kind === "file" && isDataFileUrl(value)) return true;
1175
1183
  if (isProtocolRelativeUrl(value)) return false;
@@ -1194,10 +1202,251 @@ function sanitizeUEditorUrl(raw, kind) {
1194
1202
  return "";
1195
1203
  }
1196
1204
 
1205
+ // src/components/UEditor/async-insertion-position.ts
1206
+ function trackEditorInsertionPosition(editor, initialPosition = editor.state.selection.from) {
1207
+ let position = initialPosition;
1208
+ const mapPosition = ({ transaction }) => {
1209
+ position = transaction.mapping.map(position, 1);
1210
+ };
1211
+ editor.on("transaction", mapPosition);
1212
+ return {
1213
+ get current() {
1214
+ return position;
1215
+ },
1216
+ stop() {
1217
+ editor.off("transaction", mapPosition);
1218
+ }
1219
+ };
1220
+ }
1221
+
1222
+ // src/components/UEditor/image-file-upload.ts
1223
+ function fileToDataUrl(file) {
1224
+ return new Promise((resolve, reject) => {
1225
+ const reader = new FileReader();
1226
+ reader.onload = () => resolve(String(reader.result ?? ""));
1227
+ reader.onerror = () => reject(reader.error ?? new Error("Failed to read image file."));
1228
+ reader.readAsDataURL(file);
1229
+ });
1230
+ }
1231
+ async function resolveImageSource(file, options) {
1232
+ if (options.insertMode === "upload") {
1233
+ if (!options.upload) {
1234
+ if (!options.fallbackToDataUrl) {
1235
+ throw new Error("Image upload handler is not configured.");
1236
+ }
1237
+ } else {
1238
+ try {
1239
+ const uploadedUrl = await options.upload(file);
1240
+ const safeUploadedUrl = sanitizeUEditorUrl(uploadedUrl, "image");
1241
+ if (safeUploadedUrl) return safeUploadedUrl;
1242
+ if (!options.fallbackToDataUrl) {
1243
+ throw new Error("Image upload returned an invalid URL.");
1244
+ }
1245
+ } catch (error) {
1246
+ if (!options.fallbackToDataUrl) throw error;
1247
+ }
1248
+ }
1249
+ }
1250
+ const dataUrl = await fileToDataUrl(file);
1251
+ const safeDataUrl = sanitizeUEditorUrl(dataUrl, "image");
1252
+ if (!safeDataUrl) throw new Error("Image file could not be converted to a safe data URL.");
1253
+ return safeDataUrl;
1254
+ }
1255
+ async function resolveUEditorImageFiles(files, options) {
1256
+ let hadError = false;
1257
+ const reportError = (error, file) => {
1258
+ hadError = true;
1259
+ options.onError?.(error, file);
1260
+ };
1261
+ const validFiles = files.filter((file) => {
1262
+ if (!file.type.startsWith("image/") || options.allowedMimeTypes.length > 0 && !options.allowedMimeTypes.includes(file.type)) {
1263
+ reportError(new Error(`Unsupported image type: ${file.type || "unknown"}.`), file);
1264
+ return false;
1265
+ }
1266
+ if (file.size > options.maxFileSize) {
1267
+ reportError(new Error(`Image exceeds the ${options.maxFileSize}-byte size limit.`), file);
1268
+ return false;
1269
+ }
1270
+ return true;
1271
+ });
1272
+ const results = await Promise.all(validFiles.map(async (file) => {
1273
+ try {
1274
+ return { file, src: await resolveImageSource(file, options) };
1275
+ } catch (error) {
1276
+ reportError(error instanceof Error ? error : new Error("Image upload failed."), file);
1277
+ return null;
1278
+ }
1279
+ }));
1280
+ return {
1281
+ images: results.filter((image) => image !== null),
1282
+ hadError
1283
+ };
1284
+ }
1285
+
1197
1286
  // src/components/UEditor/clipboard-images.ts
1198
1287
  import { Extension } from "@tiptap/core";
1199
1288
  import { Plugin } from "@tiptap/pm/state";
1200
1289
 
1290
+ // src/components/UEditor/table-width-model.ts
1291
+ var TABLE_WIDTH_BASIS_POINTS = 1e4;
1292
+ var DEFAULT_RESPONSIVE_TABLE_WIDTH_BP = TABLE_WIDTH_BASIS_POINTS;
1293
+ var MAX_RESPONSIVE_TABLE_WIDTH_BP = 1e5;
1294
+ function positiveNumber(value, fallback) {
1295
+ const number = Number(value);
1296
+ return Number.isFinite(number) && number > 0 ? number : fallback;
1297
+ }
1298
+ function clampTableBasisPoints(value, fallback = TABLE_WIDTH_BASIS_POINTS, maximum = TABLE_WIDTH_BASIS_POINTS) {
1299
+ const number = Number(value);
1300
+ if (!Number.isFinite(number)) return fallback;
1301
+ return Math.min(maximum, Math.max(0, Math.round(number)));
1302
+ }
1303
+ function clampResponsiveTableWidthBp(value, fallback = DEFAULT_RESPONSIVE_TABLE_WIDTH_BP) {
1304
+ return Math.max(1, clampTableBasisPoints(value, fallback, MAX_RESPONSIVE_TABLE_WIDTH_BP));
1305
+ }
1306
+ function normalizeTableWidthMode(value) {
1307
+ return value === "responsive" || value === "full" ? "responsive" : "fixed";
1308
+ }
1309
+ function parsePercentageToBasisPoints(value, maximum = MAX_RESPONSIVE_TABLE_WIDTH_BP) {
1310
+ if (!value) return null;
1311
+ const match = value.trim().match(/^(-?\d+(?:\.\d+)?)%$/);
1312
+ if (!match) return null;
1313
+ const percentage = Number.parseFloat(match[1]);
1314
+ if (!Number.isFinite(percentage)) return null;
1315
+ return clampTableBasisPoints(percentage * 100, TABLE_WIDTH_BASIS_POINTS, maximum);
1316
+ }
1317
+ function formatBasisPointsAsPercentage(value) {
1318
+ const percentage = clampTableBasisPoints(value, 0, MAX_RESPONSIVE_TABLE_WIDTH_BP) / 100;
1319
+ return `${Number.parseFloat(percentage.toFixed(2))}%`;
1320
+ }
1321
+ function parseColumnRatios(value) {
1322
+ const parts = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
1323
+ const ratios = parts.map((part) => Number(part));
1324
+ return ratios.length > 0 && ratios.every((ratio) => Number.isFinite(ratio) && ratio > 0) ? ratios : null;
1325
+ }
1326
+ function normalizeColumnRatios(values, columnCount) {
1327
+ if (columnCount <= 0) return [];
1328
+ const source = values?.length === columnCount ? values.map((value) => positiveNumber(value, 1)) : Array.from({ length: columnCount }, () => 1);
1329
+ const sourceTotal = source.reduce((sum, value) => sum + value, 0);
1330
+ const normalized = source.map((value) => Math.max(1, Math.round(value / sourceTotal * TABLE_WIDTH_BASIS_POINTS)));
1331
+ let difference = TABLE_WIDTH_BASIS_POINTS - normalized.reduce((sum, value) => sum + value, 0);
1332
+ while (difference !== 0) {
1333
+ let changed = false;
1334
+ for (let index = normalized.length - 1; index >= 0 && difference !== 0; index -= 1) {
1335
+ if (difference < 0 && normalized[index] <= 1) continue;
1336
+ normalized[index] += difference > 0 ? 1 : -1;
1337
+ difference += difference > 0 ? -1 : 1;
1338
+ changed = true;
1339
+ }
1340
+ if (!changed) break;
1341
+ }
1342
+ return normalized;
1343
+ }
1344
+ function getLogicalTableColumnCount(table) {
1345
+ const firstRow = table.firstChild;
1346
+ if (!firstRow) return 0;
1347
+ let count = 0;
1348
+ firstRow.forEach((cell) => {
1349
+ count += Math.max(1, Number(cell.attrs.colspan) || 1);
1350
+ });
1351
+ return count;
1352
+ }
1353
+ function getLegacyTableColumnWeights(table, fallback = 100) {
1354
+ const weights = [];
1355
+ const firstRow = table.firstChild;
1356
+ if (!firstRow) return weights;
1357
+ firstRow.forEach((cell) => {
1358
+ const colspan = Math.max(1, Number(cell.attrs.colspan) || 1);
1359
+ const colwidth = Array.isArray(cell.attrs.colwidth) ? cell.attrs.colwidth : [];
1360
+ for (let index = 0; index < colspan; index += 1) {
1361
+ weights.push(positiveNumber(colwidth[index], fallback));
1362
+ }
1363
+ });
1364
+ return weights;
1365
+ }
1366
+ function getTableColumnRatios(table) {
1367
+ const columnCount = getLogicalTableColumnCount(table);
1368
+ const stored = parseColumnRatios(table.attrs.columnRatios);
1369
+ return normalizeColumnRatios(
1370
+ stored?.length === columnCount ? stored : getLegacyTableColumnWeights(table),
1371
+ columnCount
1372
+ );
1373
+ }
1374
+ function getResponsiveTableWidthBp(table) {
1375
+ return clampResponsiveTableWidthBp(table.attrs.widthBp);
1376
+ }
1377
+ function getResponsiveTableOffsetBp(table) {
1378
+ const width = getResponsiveTableWidthBp(table);
1379
+ return resolveResponsiveTableOffsetBp(width, null, table.attrs.offsetBp);
1380
+ }
1381
+ function resolveResponsiveTableOffsetBp(widthBp, tableAlign, fallbackOffsetBp = 0) {
1382
+ const availableGap = Math.max(
1383
+ 0,
1384
+ TABLE_WIDTH_BASIS_POINTS - clampResponsiveTableWidthBp(widthBp)
1385
+ );
1386
+ if (tableAlign === "center") return Math.round(availableGap / 2);
1387
+ if (tableAlign === "right") return availableGap;
1388
+ if (tableAlign === "left") return 0;
1389
+ return Math.min(availableGap, clampTableBasisPoints(fallbackOffsetBp, 0));
1390
+ }
1391
+ function insertResponsiveColumnRatio(ratios, insertIndex, sourceIndex) {
1392
+ if (ratios.length === 0) return [TABLE_WIDTH_BASIS_POINTS];
1393
+ const safeSourceIndex = Math.max(0, Math.min(sourceIndex, ratios.length - 1));
1394
+ const next = [...ratios];
1395
+ const sourceRatio = next[safeSourceIndex];
1396
+ next.splice(Math.max(0, Math.min(insertIndex, next.length)), 0, sourceRatio);
1397
+ return normalizeColumnRatios(next, next.length);
1398
+ }
1399
+ function deleteResponsiveColumnRatio(ratios, deleteIndex) {
1400
+ if (ratios.length <= 1) return [];
1401
+ const next = ratios.filter((_, index) => index !== deleteIndex);
1402
+ return normalizeColumnRatios(next, next.length);
1403
+ }
1404
+ function moveResponsiveColumnRatio(ratios, from, to) {
1405
+ if (from === to || from < 0 || from >= ratios.length || to < 0 || to >= ratios.length) return [...ratios];
1406
+ const next = [...ratios];
1407
+ const [moved] = next.splice(from, 1);
1408
+ next.splice(to, 0, moved);
1409
+ return normalizeColumnRatios(next, next.length);
1410
+ }
1411
+ function insertResponsiveColumnLayout(widthBp, ratios, insertIndex, sourceIndex) {
1412
+ if (ratios.length === 0) {
1413
+ return {
1414
+ widthBp: clampResponsiveTableWidthBp(widthBp),
1415
+ columnRatios: [TABLE_WIDTH_BASIS_POINTS]
1416
+ };
1417
+ }
1418
+ const normalized = normalizeColumnRatios(ratios, ratios.length);
1419
+ const safeSourceIndex = Math.max(0, Math.min(sourceIndex, normalized.length - 1));
1420
+ const sourceRatio = normalized[safeSourceIndex];
1421
+ return {
1422
+ // Growing by the source column's share preserves every existing column's
1423
+ // rendered width instead of squeezing the whole table back into 100%.
1424
+ widthBp: clampResponsiveTableWidthBp(
1425
+ clampResponsiveTableWidthBp(widthBp) * (TABLE_WIDTH_BASIS_POINTS + sourceRatio) / TABLE_WIDTH_BASIS_POINTS
1426
+ ),
1427
+ columnRatios: insertResponsiveColumnRatio(normalized, insertIndex, safeSourceIndex)
1428
+ };
1429
+ }
1430
+ function deleteResponsiveColumnLayout(widthBp, ratios, deleteIndex) {
1431
+ const normalized = normalizeColumnRatios(ratios, ratios.length);
1432
+ if (normalized.length <= 1 || deleteIndex < 0 || deleteIndex >= normalized.length) {
1433
+ return {
1434
+ widthBp: clampResponsiveTableWidthBp(widthBp),
1435
+ columnRatios: normalized.length <= 1 ? [] : normalized
1436
+ };
1437
+ }
1438
+ const remainingRatio = TABLE_WIDTH_BASIS_POINTS - normalized[deleteIndex];
1439
+ const nextWidthBp = clampResponsiveTableWidthBp(
1440
+ clampResponsiveTableWidthBp(widthBp) * remainingRatio / TABLE_WIDTH_BASIS_POINTS
1441
+ );
1442
+ return {
1443
+ // Deletion is the inverse operation: remaining columns keep their rendered
1444
+ // widths while the table gives the removed column's space back.
1445
+ widthBp: Math.abs(nextWidthBp - DEFAULT_RESPONSIVE_TABLE_WIDTH_BP) <= 1 ? DEFAULT_RESPONSIVE_TABLE_WIDTH_BP : nextWidthBp,
1446
+ columnRatios: deleteResponsiveColumnRatio(normalized, deleteIndex)
1447
+ };
1448
+ }
1449
+
1201
1450
  // src/components/UEditor/clipboard-tables.ts
1202
1451
  var DEFAULT_HTML_TABLE_CELL_BACKGROUND_COLOR = "#ffffff";
1203
1452
  var DEFAULT_HTML_TABLE_TEXT_COLOR = "#000000";
@@ -1829,13 +2078,34 @@ function createNormalizedRowContent(row, columnCount, fillerCellAttrs) {
1829
2078
  }
1830
2079
  return content;
1831
2080
  }
1832
- function createTableContent(rows, minColumnCount = 1, fillerCellAttrs) {
2081
+ function createTableContent(rows, minColumnCount = 1, fillerCellAttrs, layout) {
1833
2082
  const tableRows = rows.filter((row) => row.cells.length > 0);
1834
2083
  if (tableRows.length === 0) return null;
1835
2084
  const { positionedRows, columnCount } = normalizeTableRows(tableRows);
1836
2085
  if (columnCount < minColumnCount) return null;
2086
+ const inferredColumnWeights = Array.from({ length: columnCount }, () => 100);
2087
+ positionedRows.forEach((row) => {
2088
+ row.cells.forEach(({ cell, colspan, startColumn }) => {
2089
+ const width = colspan === 1 ? cell.attrs?.colwidth?.[0] : null;
2090
+ if (typeof width === "number" && Number.isFinite(width) && width > 0) {
2091
+ inferredColumnWeights[startColumn] = width;
2092
+ }
2093
+ });
2094
+ });
2095
+ const columnRatios = normalizeColumnRatios(
2096
+ layout?.columnRatios?.length === columnCount ? layout.columnRatios : inferredColumnWeights,
2097
+ columnCount
2098
+ );
2099
+ const widthBp = clampResponsiveTableWidthBp(layout?.widthBp, DEFAULT_RESPONSIVE_TABLE_WIDTH_BP);
2100
+ const offsetBp = resolveResponsiveTableOffsetBp(widthBp, null, layout?.offsetBp);
1837
2101
  return {
1838
2102
  type: "table",
2103
+ attrs: {
2104
+ widthMode: "responsive",
2105
+ widthBp,
2106
+ offsetBp,
2107
+ columnRatios
2108
+ },
1839
2109
  content: positionedRows.map((row) => ({
1840
2110
  type: "tableRow",
1841
2111
  ...row.attrs ? { attrs: row.attrs } : {},
@@ -1856,9 +2126,22 @@ function getClipboardTableContent(dataTransfer) {
1856
2126
  if (tables.length !== 1 || hasMeaningfulContentOutsideTable(sourceBody)) return null;
1857
2127
  const table = tables[0];
1858
2128
  if (!(table instanceof HTMLTableElement)) return null;
1859
- return createTableContent(getHtmlTableRows(table, styleMap), 1, {
1860
- backgroundColor: DEFAULT_HTML_TABLE_CELL_BACKGROUND_COLOR
1861
- });
2129
+ const storedWidthValue = table.getAttribute("data-table-width-bp");
2130
+ const storedOffsetValue = table.getAttribute("data-table-offset-bp");
2131
+ const storedWidthBp = Number(storedWidthValue);
2132
+ const storedOffsetBp = Number(storedOffsetValue);
2133
+ const widthBp = storedWidthValue !== null && Number.isFinite(storedWidthBp) && storedWidthBp > 0 ? storedWidthBp : parsePercentageToBasisPoints(table.getAttribute("data-table-width") ?? table.style.width) ?? DEFAULT_RESPONSIVE_TABLE_WIDTH_BP;
2134
+ const offsetBp = storedOffsetValue !== null && Number.isFinite(storedOffsetBp) && storedOffsetBp >= 0 ? storedOffsetBp : parsePercentageToBasisPoints(table.getAttribute("data-table-offset") ?? table.style.marginLeft) ?? 0;
2135
+ return createTableContent(
2136
+ getHtmlTableRows(table, styleMap),
2137
+ 1,
2138
+ { backgroundColor: DEFAULT_HTML_TABLE_CELL_BACKGROUND_COLOR },
2139
+ {
2140
+ widthBp,
2141
+ offsetBp,
2142
+ columnRatios: parseColumnRatios(table.getAttribute("data-table-column-ratios"))
2143
+ }
2144
+ );
1862
2145
  }
1863
2146
  function parseClipboardTsvRows(text) {
1864
2147
  const rows = [];
@@ -1944,26 +2227,6 @@ function getImageFiles(dataTransfer) {
1944
2227
  }
1945
2228
  return Array.from(byKey.values());
1946
2229
  }
1947
- function fileToDataUrl(file) {
1948
- return new Promise((resolve, reject) => {
1949
- const reader = new FileReader();
1950
- reader.onload = () => resolve(String(reader.result ?? ""));
1951
- reader.onerror = () => reject(reader.error ?? new Error("Failed to read image file"));
1952
- reader.readAsDataURL(file);
1953
- });
1954
- }
1955
- async function resolveImageSrc(file, options) {
1956
- if (options.insertMode === "upload" && options.upload) {
1957
- try {
1958
- const result = await options.upload(file);
1959
- const src = typeof result === "string" ? sanitizeUEditorUrl(result, "image") : "";
1960
- if (src) return src;
1961
- } catch (err) {
1962
- if (!options.fallbackToDataUrl) throw err;
1963
- }
1964
- }
1965
- return fileToDataUrl(file);
1966
- }
1967
2230
  var ClipboardImages = Extension.create({
1968
2231
  name: "clipboardImages",
1969
2232
  addOptions() {
@@ -1972,25 +2235,32 @@ var ClipboardImages = Extension.create({
1972
2235
  allowedMimeTypes: DEFAULT_UEDITOR_IMAGE_MIME_TYPES,
1973
2236
  upload: void 0,
1974
2237
  fallbackToDataUrl: true,
1975
- insertMode: "base64"
2238
+ insertMode: "base64",
2239
+ onError: void 0
1976
2240
  };
1977
2241
  },
1978
2242
  addProseMirrorPlugins() {
1979
2243
  const editor = this.editor;
1980
2244
  const options = this.options;
1981
2245
  const insertFiles = async (files, selectionPos) => {
1982
- if (selectionPos !== void 0) {
1983
- editor.commands.setTextSelection(selectionPos);
1984
- }
1985
- for (const file of files) {
1986
- if (file.size > options.maxFileSize) continue;
1987
- if (options.allowedMimeTypes.length > 0 && !options.allowedMimeTypes.includes(file.type)) continue;
1988
- try {
1989
- const src = await resolveImageSrc(file, options);
1990
- editor.chain().focus().setImage({ src, alt: file.name }).run();
1991
- editor.commands.createParagraphNear();
1992
- } catch {
1993
- }
2246
+ const trackedPosition = trackEditorInsertionPosition(editor, selectionPos);
2247
+ try {
2248
+ const { images } = await resolveUEditorImageFiles(files, {
2249
+ maxFileSize: options.maxFileSize,
2250
+ allowedMimeTypes: options.allowedMimeTypes,
2251
+ upload: options.upload,
2252
+ fallbackToDataUrl: options.fallbackToDataUrl,
2253
+ insertMode: options.insertMode,
2254
+ onError: options.onError
2255
+ });
2256
+ if (editor.isDestroyed || images.length === 0) return;
2257
+ const content = images.map((image) => ({
2258
+ type: "image",
2259
+ attrs: { src: image.src, alt: image.file.name }
2260
+ }));
2261
+ editor.commands.insertContentAt(trackedPosition.current, content, { updateSelection: false });
2262
+ } finally {
2263
+ trackedPosition.stop();
1994
2264
  }
1995
2265
  };
1996
2266
  return [
@@ -2019,7 +2289,8 @@ var ClipboardImages = Extension.create({
2019
2289
  },
2020
2290
  handleDrop: (view, event, _slice, moved) => {
2021
2291
  if (moved) return false;
2022
- if (!(event instanceof DragEvent)) return false;
2292
+ const DragEventCtor = view.dom.ownerDocument.defaultView?.DragEvent;
2293
+ if (!DragEventCtor || !(event instanceof DragEventCtor)) return false;
2023
2294
  const files = getImageFiles(event.dataTransfer);
2024
2295
  if (files.length === 0) return false;
2025
2296
  const pos = view.posAtCoords({ left: event.clientX, top: event.clientY })?.pos;
@@ -2049,6 +2320,7 @@ var LinkInput = ({
2049
2320
  const [url, setUrl] = useState3(initialUrl);
2050
2321
  const [error, setError] = useState3("");
2051
2322
  const inputRef = useRef2(null);
2323
+ const inputId = useId();
2052
2324
  const errorId = useId();
2053
2325
  useEffect3(() => {
2054
2326
  inputRef.current?.focus();
@@ -2065,12 +2337,17 @@ var LinkInput = ({
2065
2337
  onSubmit(normalized);
2066
2338
  };
2067
2339
  return /* @__PURE__ */ jsxs3("form", { onSubmit: handleSubmit, className: "p-2", children: [
2340
+ /* @__PURE__ */ jsx4("label", { htmlFor: inputId, className: "sr-only", children: t("toolbar.link") }),
2068
2341
  /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
2069
2342
  /* @__PURE__ */ jsx4(
2070
2343
  "input",
2071
2344
  {
2072
2345
  ref: inputRef,
2346
+ id: inputId,
2073
2347
  type: "text",
2348
+ inputMode: "url",
2349
+ name: "link-url",
2350
+ autoComplete: "off",
2074
2351
  value: url,
2075
2352
  onChange: (e) => {
2076
2353
  setUrl(e.target.value);
@@ -2082,8 +2359,8 @@ var LinkInput = ({
2082
2359
  className: `flex-1 rounded-lg bg-muted/50 px-3 py-2 text-sm ${formControlOutlineClass} aria-invalid:border-destructive`
2083
2360
  }
2084
2361
  ),
2085
- /* @__PURE__ */ jsx4("button", { type: "submit", className: "p-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors", children: /* @__PURE__ */ jsx4(Check, { className: "w-4 h-4" }) }),
2086
- /* @__PURE__ */ jsx4("button", { type: "button", onClick: onCancel, className: "p-2 rounded-lg hover:bg-muted transition-colors text-muted-foreground", children: /* @__PURE__ */ jsx4(X, { className: "w-4 h-4" }) })
2362
+ /* @__PURE__ */ jsx4("button", { type: "submit", "aria-label": t("toolbar.link"), className: "p-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors", children: /* @__PURE__ */ jsx4(Check, { "aria-hidden": "true", className: "w-4 h-4" }) }),
2363
+ /* @__PURE__ */ jsx4("button", { type: "button", "aria-label": t("imageInput.cancelBtn"), onClick: onCancel, className: "p-2 rounded-lg hover:bg-muted transition-colors text-muted-foreground", children: /* @__PURE__ */ jsx4(X, { "aria-hidden": "true", className: "w-4 h-4" }) })
2087
2364
  ] }),
2088
2365
  error ? /* @__PURE__ */ jsx4("p", { id: errorId, role: "alert", className: "mt-1.5 px-1 text-xs text-destructive", children: error }) : null
2089
2366
  ] });
@@ -2092,7 +2369,11 @@ var ImageInput = ({ onSubmit, onCancel }) => {
2092
2369
  const t = useSmartTranslations("UEditor");
2093
2370
  const [url, setUrl] = useState3("");
2094
2371
  const [alt, setAlt] = useState3("");
2372
+ const [error, setError] = useState3("");
2095
2373
  const inputRef = useRef2(null);
2374
+ const urlId = useId();
2375
+ const altId = useId();
2376
+ const errorId = useId();
2096
2377
  useEffect3(() => {
2097
2378
  inputRef.current?.focus();
2098
2379
  }, []);
@@ -2100,30 +2381,45 @@ var ImageInput = ({ onSubmit, onCancel }) => {
2100
2381
  e.preventDefault();
2101
2382
  const safeUrl = sanitizeUEditorUrl(url, "image");
2102
2383
  if (safeUrl) {
2384
+ setError("");
2103
2385
  onSubmit(safeUrl, alt);
2386
+ } else {
2387
+ setError(t("imageInput.invalid"));
2104
2388
  }
2105
2389
  };
2106
2390
  return /* @__PURE__ */ jsxs3("form", { onSubmit: handleSubmit, className: "p-3 space-y-3", children: [
2107
2391
  /* @__PURE__ */ jsxs3("div", { children: [
2108
- /* @__PURE__ */ jsx4("label", { className: "text-xs font-medium text-muted-foreground", children: t("imageInput.urlLabel") }),
2392
+ /* @__PURE__ */ jsx4("label", { htmlFor: urlId, className: "text-xs font-medium text-muted-foreground", children: t("imageInput.urlLabel") }),
2109
2393
  /* @__PURE__ */ jsx4(
2110
2394
  "input",
2111
2395
  {
2112
2396
  ref: inputRef,
2397
+ id: urlId,
2113
2398
  type: "text",
2399
+ inputMode: "url",
2400
+ name: "image-url",
2401
+ autoComplete: "off",
2114
2402
  value: url,
2115
- onChange: (e) => setUrl(e.target.value),
2403
+ onChange: (e) => {
2404
+ setUrl(e.target.value);
2405
+ if (error) setError("");
2406
+ },
2116
2407
  placeholder: t("imageInput.urlPlaceholder"),
2408
+ "aria-invalid": Boolean(error),
2409
+ "aria-describedby": error ? errorId : void 0,
2117
2410
  className: `mt-1 w-full rounded-lg bg-muted/50 px-3 py-2 text-sm ${formControlOutlineClass}`
2118
2411
  }
2119
2412
  )
2120
2413
  ] }),
2121
2414
  /* @__PURE__ */ jsxs3("div", { children: [
2122
- /* @__PURE__ */ jsx4("label", { className: "text-xs font-medium text-muted-foreground", children: t("imageInput.altLabel") }),
2415
+ /* @__PURE__ */ jsx4("label", { htmlFor: altId, className: "text-xs font-medium text-muted-foreground", children: t("imageInput.altLabel") }),
2123
2416
  /* @__PURE__ */ jsx4(
2124
2417
  "input",
2125
2418
  {
2419
+ id: altId,
2126
2420
  type: "text",
2421
+ name: "image-alt",
2422
+ autoComplete: "off",
2127
2423
  value: alt,
2128
2424
  onChange: (e) => setAlt(e.target.value),
2129
2425
  placeholder: t("imageInput.altPlaceholder"),
@@ -2131,6 +2427,7 @@ var ImageInput = ({ onSubmit, onCancel }) => {
2131
2427
  }
2132
2428
  )
2133
2429
  ] }),
2430
+ error ? /* @__PURE__ */ jsx4("p", { id: errorId, role: "alert", className: "text-xs text-destructive", children: error }) : null,
2134
2431
  /* @__PURE__ */ jsxs3("div", { className: "flex gap-2", children: [
2135
2432
  /* @__PURE__ */ jsx4(
2136
2433
  "button",
@@ -2986,10 +3283,14 @@ function findTableNodeInfoFromState(state, anchorPos) {
2986
3283
  function applyTableAlignment(editor, tableAlign, anchorPos) {
2987
3284
  const tableInfo = findTableNodeInfoFromState(editor.state, anchorPos);
2988
3285
  if (!tableInfo) return false;
3286
+ const responsive = normalizeTableWidthMode(tableInfo.node.attrs.widthMode) === "responsive";
3287
+ const widthBp = getResponsiveTableWidthBp(tableInfo.node);
3288
+ const offsetBp = resolveResponsiveTableOffsetBp(widthBp, tableAlign);
2989
3289
  editor.view.dispatch(
2990
3290
  editor.state.tr.setNodeMarkup(tableInfo.pos, tableInfo.node.type, {
2991
3291
  ...tableInfo.node.attrs,
2992
- textAlign: tableAlign
3292
+ textAlign: tableAlign,
3293
+ ...responsive ? { offsetBp } : null
2993
3294
  })
2994
3295
  );
2995
3296
  const domAtTable = editor.view.domAtPos(Math.min(tableInfo.pos + 1, editor.state.doc.content.size)).node;
@@ -3006,8 +3307,8 @@ function applyTableAlignment(editor, tableAlign, anchorPos) {
3006
3307
  }
3007
3308
  if (tableAlign) {
3008
3309
  tableElement.setAttribute("data-table-align", tableAlign);
3009
- tableElement.style.marginLeft = tableAlign === "center" || tableAlign === "right" ? "auto" : "0";
3010
- tableElement.style.marginRight = tableAlign === "center" ? "auto" : tableAlign === "right" ? "0" : "auto";
3310
+ tableElement.style.marginLeft = responsive ? formatBasisPointsAsPercentage(offsetBp) : tableAlign === "center" || tableAlign === "right" ? "auto" : "0";
3311
+ tableElement.style.marginRight = responsive ? "auto" : tableAlign === "center" ? "auto" : tableAlign === "right" ? "0" : "auto";
3011
3312
  } else {
3012
3313
  tableElement.removeAttribute("data-table-align");
3013
3314
  tableElement.style.removeProperty("margin-left");
@@ -3020,6 +3321,609 @@ function applyTableAlignment(editor, tableAlign, anchorPos) {
3020
3321
  // src/components/UEditor/table-cell-commands.ts
3021
3322
  import { TextSelection as TextSelection3 } from "@tiptap/pm/state";
3022
3323
  import { selectedRect, TableMap } from "@tiptap/pm/tables";
3324
+
3325
+ // src/components/UEditor/table-formula.ts
3326
+ var CELL_ADDRESS_RE = /^(\$?)([A-Z]+)(\$?)([1-9]\d*)$/i;
3327
+ var CELL_RANGE_RE = /^(\$?[A-Z]+\$?[1-9]\d*):(\$?[A-Z]+\$?[1-9]\d*)$/i;
3328
+ var SUPPORTED_FUNCTIONS = /* @__PURE__ */ new Set(["SUM", "AVG", "MIN", "MAX", "COUNT"]);
3329
+ function columnNameToIndex(columnName) {
3330
+ const normalized = columnName.trim().toUpperCase();
3331
+ if (!/^[A-Z]+$/.test(normalized)) {
3332
+ return -1;
3333
+ }
3334
+ let index = 0;
3335
+ for (const char of normalized) {
3336
+ index = index * 26 + char.charCodeAt(0) - 64;
3337
+ }
3338
+ return index - 1;
3339
+ }
3340
+ function indexToColumnName(index) {
3341
+ if (!Number.isInteger(index) || index < 0) {
3342
+ return "";
3343
+ }
3344
+ let value = index + 1;
3345
+ let name = "";
3346
+ while (value > 0) {
3347
+ const remainder = (value - 1) % 26;
3348
+ name = String.fromCharCode(65 + remainder) + name;
3349
+ value = Math.floor((value - 1) / 26);
3350
+ }
3351
+ return name;
3352
+ }
3353
+ function parseTableCellAddress(input) {
3354
+ const match = input.trim().match(CELL_ADDRESS_RE);
3355
+ if (!match) {
3356
+ return null;
3357
+ }
3358
+ const column = columnNameToIndex(match[2] ?? "");
3359
+ const row = Number.parseInt(match[4] ?? "", 10) - 1;
3360
+ if (column < 0 || row < 0) {
3361
+ return null;
3362
+ }
3363
+ return {
3364
+ column,
3365
+ row,
3366
+ label: `${indexToColumnName(column)}${row + 1}`
3367
+ };
3368
+ }
3369
+ function parseTableCellRange(input) {
3370
+ const match = input.trim().match(CELL_RANGE_RE);
3371
+ if (!match) {
3372
+ return null;
3373
+ }
3374
+ const from = parseTableCellAddress(match[1] ?? "");
3375
+ const to = parseTableCellAddress(match[2] ?? "");
3376
+ if (!from || !to) {
3377
+ return null;
3378
+ }
3379
+ return { from, to };
3380
+ }
3381
+ function getTableCellRangeLabels(range) {
3382
+ const startColumn = Math.min(range.from.column, range.to.column);
3383
+ const endColumn = Math.max(range.from.column, range.to.column);
3384
+ const startRow = Math.min(range.from.row, range.to.row);
3385
+ const endRow = Math.max(range.from.row, range.to.row);
3386
+ const labels = [];
3387
+ for (let row = startRow; row <= endRow; row += 1) {
3388
+ for (let column = startColumn; column <= endColumn; column += 1) {
3389
+ labels.push(`${indexToColumnName(column)}${row + 1}`);
3390
+ }
3391
+ }
3392
+ return labels;
3393
+ }
3394
+ function normalizeTableFormula(formula) {
3395
+ return formula.trim().replace(/^=/, "").trim();
3396
+ }
3397
+ function isDraftTableFormula(formula) {
3398
+ const normalized = normalizeTableFormula(formula);
3399
+ if (!normalized) return true;
3400
+ let depth = 0;
3401
+ for (const char of normalized) {
3402
+ if (char === "(") depth += 1;
3403
+ if (char === ")") depth -= 1;
3404
+ if (depth < 0) return false;
3405
+ }
3406
+ if (depth > 0) return true;
3407
+ if (/^[A-Z]+\(\s*\)$/i.test(normalized)) return true;
3408
+ if (/[+\-*/,(]\s*$/.test(normalized)) return true;
3409
+ return false;
3410
+ }
3411
+ function formatFormulaError(error) {
3412
+ return `#${error.toUpperCase()}`;
3413
+ }
3414
+ function normalizeTableNumberFormat(format) {
3415
+ return format === "text" || format === "number" || format === "currency" || format === "percent" || format === "date" ? format : "text";
3416
+ }
3417
+ function formatTableFormulaDisplayValue(value, numberFormat) {
3418
+ const stringValue = String(value);
3419
+ if (stringValue.startsWith("#")) {
3420
+ return stringValue;
3421
+ }
3422
+ const normalizedFormat = normalizeTableNumberFormat(numberFormat);
3423
+ if (normalizedFormat === "text") {
3424
+ return stringValue;
3425
+ }
3426
+ const numericValue = typeof value === "number" ? value : Number.parseFloat(stringValue.replace(/,/g, ""));
3427
+ if (!Number.isFinite(numericValue)) {
3428
+ return stringValue;
3429
+ }
3430
+ if (normalizedFormat === "number") {
3431
+ return new Intl.NumberFormat("en-US", { maximumFractionDigits: 6 }).format(numericValue);
3432
+ }
3433
+ if (normalizedFormat === "currency") {
3434
+ return new Intl.NumberFormat("en-US", {
3435
+ style: "currency",
3436
+ currency: "USD",
3437
+ maximumFractionDigits: 2
3438
+ }).format(numericValue);
3439
+ }
3440
+ if (normalizedFormat === "percent") {
3441
+ return new Intl.NumberFormat("en-US", {
3442
+ style: "percent",
3443
+ maximumFractionDigits: 2
3444
+ }).format(numericValue);
3445
+ }
3446
+ const excelEpochMs = Date.UTC(1899, 11, 30);
3447
+ const date = new Date(excelEpochMs + numericValue * 24 * 60 * 60 * 1e3);
3448
+ if (Number.isNaN(date.getTime())) {
3449
+ return stringValue;
3450
+ }
3451
+ return new Intl.DateTimeFormat("en-US", {
3452
+ year: "numeric",
3453
+ month: "2-digit",
3454
+ day: "2-digit",
3455
+ timeZone: "UTC"
3456
+ }).format(date);
3457
+ }
3458
+ function getTableFormulaReferences(formula) {
3459
+ const normalized = normalizeTableFormula(formula);
3460
+ if (!normalized) return [];
3461
+ const tokens = tokenizeFormula(normalized);
3462
+ if (!tokens) return [];
3463
+ const references = /* @__PURE__ */ new Set();
3464
+ for (const token of tokens) {
3465
+ if (token.type === "cell") {
3466
+ const address = parseTableCellAddress(token.value);
3467
+ if (address) references.add(address.label);
3468
+ } else if (token.type === "range") {
3469
+ const range = parseTableCellRange(token.value);
3470
+ if (!range) continue;
3471
+ for (const label of getTableCellRangeLabels(range)) {
3472
+ references.add(label);
3473
+ }
3474
+ }
3475
+ }
3476
+ return Array.from(references);
3477
+ }
3478
+ function buildTableFormulaDependencyGraph(cells) {
3479
+ const dependencies = /* @__PURE__ */ new Map();
3480
+ const dependents = /* @__PURE__ */ new Map();
3481
+ const formulas = /* @__PURE__ */ new Map();
3482
+ for (const cell of cells) {
3483
+ const label = cell.label.toUpperCase();
3484
+ formulas.set(label, cell.formula);
3485
+ dependencies.set(label, new Set(getTableFormulaReferences(cell.formula)));
3486
+ if (!dependents.has(label)) {
3487
+ dependents.set(label, /* @__PURE__ */ new Set());
3488
+ }
3489
+ }
3490
+ for (const [label, refs] of dependencies) {
3491
+ for (const ref of refs) {
3492
+ if (!dependents.has(ref)) {
3493
+ dependents.set(ref, /* @__PURE__ */ new Set());
3494
+ }
3495
+ dependents.get(ref)?.add(label);
3496
+ }
3497
+ }
3498
+ return { dependencies, dependents, formulas };
3499
+ }
3500
+ function getTableFormulaCircularReferences(graph) {
3501
+ const circular = /* @__PURE__ */ new Set();
3502
+ const visited = /* @__PURE__ */ new Set();
3503
+ const finishOrder = [];
3504
+ for (const startLabel of graph.formulas.keys()) {
3505
+ if (visited.has(startLabel)) continue;
3506
+ visited.add(startLabel);
3507
+ const stack = [
3508
+ {
3509
+ label: startLabel,
3510
+ references: Array.from(graph.dependencies.get(startLabel) ?? []).filter((ref) => graph.formulas.has(ref)),
3511
+ nextIndex: 0
3512
+ }
3513
+ ];
3514
+ while (stack.length > 0) {
3515
+ const frame = stack[stack.length - 1];
3516
+ if (!frame) break;
3517
+ const reference = frame.references[frame.nextIndex];
3518
+ if (reference) {
3519
+ frame.nextIndex += 1;
3520
+ if (!visited.has(reference)) {
3521
+ visited.add(reference);
3522
+ stack.push({
3523
+ label: reference,
3524
+ references: Array.from(graph.dependencies.get(reference) ?? []).filter((ref) => graph.formulas.has(ref)),
3525
+ nextIndex: 0
3526
+ });
3527
+ }
3528
+ continue;
3529
+ }
3530
+ stack.pop();
3531
+ finishOrder.push(frame.label);
3532
+ }
3533
+ }
3534
+ const assigned = /* @__PURE__ */ new Set();
3535
+ for (let index = finishOrder.length - 1; index >= 0; index -= 1) {
3536
+ const startLabel = finishOrder[index];
3537
+ if (!startLabel || assigned.has(startLabel)) continue;
3538
+ const component = [];
3539
+ const stack = [startLabel];
3540
+ assigned.add(startLabel);
3541
+ while (stack.length > 0) {
3542
+ const label = stack.pop();
3543
+ if (!label) continue;
3544
+ component.push(label);
3545
+ for (const dependent of graph.dependents.get(label) ?? []) {
3546
+ if (!graph.formulas.has(dependent) || assigned.has(dependent)) continue;
3547
+ assigned.add(dependent);
3548
+ stack.push(dependent);
3549
+ }
3550
+ }
3551
+ if (component.length > 1) {
3552
+ for (const label of component) circular.add(label);
3553
+ } else {
3554
+ const label = component[0];
3555
+ if (label && graph.dependencies.get(label)?.has(label)) circular.add(label);
3556
+ }
3557
+ }
3558
+ return circular;
3559
+ }
3560
+ function getTableFormulaRecalculationOrder(graph) {
3561
+ const circular = getTableFormulaCircularReferences(graph);
3562
+ const visiting = /* @__PURE__ */ new Set();
3563
+ const visited = /* @__PURE__ */ new Set();
3564
+ const order = [];
3565
+ for (const startLabel of graph.formulas.keys()) {
3566
+ if (visited.has(startLabel) || circular.has(startLabel)) continue;
3567
+ const stack = [];
3568
+ const push = (label) => {
3569
+ visiting.add(label);
3570
+ stack.push({
3571
+ label,
3572
+ references: Array.from(graph.dependencies.get(label) ?? []).filter((ref) => graph.formulas.has(ref) && !circular.has(ref)),
3573
+ nextIndex: 0
3574
+ });
3575
+ };
3576
+ push(startLabel);
3577
+ while (stack.length > 0) {
3578
+ const frame = stack[stack.length - 1];
3579
+ if (!frame) break;
3580
+ const reference = frame.references[frame.nextIndex];
3581
+ if (reference) {
3582
+ frame.nextIndex += 1;
3583
+ if (!visited.has(reference) && !visiting.has(reference)) {
3584
+ push(reference);
3585
+ }
3586
+ continue;
3587
+ }
3588
+ stack.pop();
3589
+ visiting.delete(frame.label);
3590
+ visited.add(frame.label);
3591
+ order.push(frame.label);
3592
+ }
3593
+ }
3594
+ return { order, circular };
3595
+ }
3596
+ function getAffectedTableFormulaLabels(graph, changedLabels) {
3597
+ const affected = /* @__PURE__ */ new Set();
3598
+ const queue = Array.from(changedLabels, (label) => label.toUpperCase());
3599
+ for (const label of queue) {
3600
+ if (graph.formulas.has(label)) {
3601
+ affected.add(label);
3602
+ }
3603
+ }
3604
+ for (let index = 0; index < queue.length; index += 1) {
3605
+ const label = queue[index];
3606
+ if (!label) continue;
3607
+ for (const dependent of graph.dependents.get(label) ?? []) {
3608
+ if (affected.has(dependent)) continue;
3609
+ affected.add(dependent);
3610
+ queue.push(dependent);
3611
+ }
3612
+ }
3613
+ return affected;
3614
+ }
3615
+ function evaluateBasicTableFormula(formula, getCellValue) {
3616
+ const normalized = normalizeTableFormula(formula);
3617
+ if (!normalized) {
3618
+ return { value: null, error: "empty" };
3619
+ }
3620
+ if (normalized.includes("#REF!")) {
3621
+ return { value: null, error: "invalid-reference" };
3622
+ }
3623
+ const tokens = tokenizeFormula(normalized);
3624
+ if (!tokens) {
3625
+ return { value: null, error: "invalid-formula" };
3626
+ }
3627
+ const parser = new FormulaParser(tokens, getCellValue);
3628
+ const result = parser.parseExpression();
3629
+ if (result.error) {
3630
+ return result;
3631
+ }
3632
+ if (!parser.isComplete()) {
3633
+ return { value: null, error: "invalid-formula" };
3634
+ }
3635
+ return result;
3636
+ }
3637
+ function tokenizeFormula(formula) {
3638
+ const tokens = [];
3639
+ let index = 0;
3640
+ while (index < formula.length) {
3641
+ const char = formula[index];
3642
+ if (!char) break;
3643
+ if (/\s/.test(char)) {
3644
+ index += 1;
3645
+ continue;
3646
+ }
3647
+ if (char === "," || char === "+" || char === "-" || char === "*" || char === "/" || char === "(" || char === ")") {
3648
+ if (char === ",") tokens.push({ type: "comma", value: char });
3649
+ else if (char === "(" || char === ")") tokens.push({ type: "paren", value: char });
3650
+ else tokens.push({ type: "operator", value: char });
3651
+ index += 1;
3652
+ continue;
3653
+ }
3654
+ const numberMatch = formula.slice(index).match(/^(?:\d+(?:\.\d*)?|\.\d+)/);
3655
+ if (numberMatch?.[0]) {
3656
+ const value = Number.parseFloat(numberMatch[0]);
3657
+ if (!Number.isFinite(value)) return null;
3658
+ tokens.push({ type: "number", value });
3659
+ index += numberMatch[0].length;
3660
+ continue;
3661
+ }
3662
+ const identifierMatch = formula.slice(index).match(/^\$?[A-Z]+\$?[1-9]\d*(?::\$?[A-Z]+\$?[1-9]\d*)?|^[A-Z]+/i);
3663
+ if (identifierMatch?.[0]) {
3664
+ const value = identifierMatch[0].toUpperCase();
3665
+ if (CELL_RANGE_RE.test(value)) tokens.push({ type: "range", value });
3666
+ else if (parseTableCellAddress(value)) tokens.push({ type: "cell", value });
3667
+ else if (SUPPORTED_FUNCTIONS.has(value)) tokens.push({ type: "function", value });
3668
+ else return null;
3669
+ index += identifierMatch[0].length;
3670
+ continue;
3671
+ }
3672
+ return null;
3673
+ }
3674
+ return tokens;
3675
+ }
3676
+ function toFiniteFormulaResult(value) {
3677
+ return Number.isFinite(value) ? { value, error: null } : { value: null, error: "invalid-formula" };
3678
+ }
3679
+ function parseTableCellNumericValue(value) {
3680
+ if (typeof value === "number") {
3681
+ return Number.isFinite(value) ? value : null;
3682
+ }
3683
+ let normalized = String(value ?? "").trim();
3684
+ if (!normalized || normalized.startsWith("#")) return null;
3685
+ const isPercent = normalized.endsWith("%");
3686
+ if (isPercent) normalized = normalized.slice(0, -1).trim();
3687
+ const currencyMatch = normalized.match(/^([+-]?)\$(.+)$/);
3688
+ if (currencyMatch) {
3689
+ normalized = `${currencyMatch[1] ?? ""}${currencyMatch[2] ?? ""}`.trim();
3690
+ }
3691
+ const groupedNumber = /^[+-]?\d{1,3}(?:,\d{3})+(?:\.\d*)?(?:[eE][+-]?\d+)?$/;
3692
+ const plainNumber = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
3693
+ if (!groupedNumber.test(normalized) && !plainNumber.test(normalized)) return null;
3694
+ const parsed = Number(normalized.replace(/,/g, ""));
3695
+ if (!Number.isFinite(parsed)) return null;
3696
+ return isPercent ? parsed / 100 : parsed;
3697
+ }
3698
+ var FormulaParser = class {
3699
+ constructor(tokens, getCellValue) {
3700
+ this.tokens = tokens;
3701
+ this.getCellValue = getCellValue;
3702
+ this.index = 0;
3703
+ }
3704
+ isComplete() {
3705
+ return this.index >= this.tokens.length;
3706
+ }
3707
+ parseExpression() {
3708
+ let left = this.parseTerm();
3709
+ while (!left.error) {
3710
+ const operator = this.peekOperator(["+", "-"]);
3711
+ if (!operator) break;
3712
+ this.index += 1;
3713
+ const right = this.parseTerm();
3714
+ if (right.error) return right;
3715
+ left = toFiniteFormulaResult(operator.value === "+" ? left.value + right.value : left.value - right.value);
3716
+ }
3717
+ return left;
3718
+ }
3719
+ parseTerm() {
3720
+ let left = this.parseFactor();
3721
+ while (!left.error) {
3722
+ const operator = this.peekOperator(["*", "/"]);
3723
+ if (!operator) break;
3724
+ this.index += 1;
3725
+ const right = this.parseFactor();
3726
+ if (right.error) return right;
3727
+ if (operator.value === "/" && right.value === 0) {
3728
+ return { value: null, error: "division-by-zero" };
3729
+ }
3730
+ left = toFiniteFormulaResult(operator.value === "*" ? left.value * right.value : left.value / right.value);
3731
+ }
3732
+ return left;
3733
+ }
3734
+ parseFactor() {
3735
+ const token = this.tokens[this.index];
3736
+ if (!token) {
3737
+ return { value: null, error: "invalid-formula" };
3738
+ }
3739
+ if (token.type === "operator" && (token.value === "-" || token.value === "+")) {
3740
+ this.index += 1;
3741
+ const value = this.parseFactor();
3742
+ if (value.error) return value;
3743
+ return toFiniteFormulaResult(token.value === "-" ? -value.value : value.value);
3744
+ }
3745
+ if (token.type === "number") {
3746
+ this.index += 1;
3747
+ return { value: token.value, error: null };
3748
+ }
3749
+ if (token.type === "cell") {
3750
+ this.index += 1;
3751
+ return this.readCellNumber(token.value);
3752
+ }
3753
+ if (token.type === "function") {
3754
+ return this.parseFunction(token.value);
3755
+ }
3756
+ if (token.type === "paren" && token.value === "(") {
3757
+ this.index += 1;
3758
+ const value = this.parseExpression();
3759
+ if (value.error) return value;
3760
+ if (!this.consumeParen(")")) {
3761
+ return { value: null, error: "invalid-formula" };
3762
+ }
3763
+ return value;
3764
+ }
3765
+ return { value: null, error: "invalid-formula" };
3766
+ }
3767
+ parseFunction(name) {
3768
+ this.index += 1;
3769
+ if (!this.consumeParen("(")) {
3770
+ return { value: null, error: "invalid-formula" };
3771
+ }
3772
+ const values = [];
3773
+ while (true) {
3774
+ const token = this.tokens[this.index];
3775
+ if (!token) {
3776
+ return { value: null, error: "invalid-formula" };
3777
+ }
3778
+ if (token.type === "range") {
3779
+ this.index += 1;
3780
+ const range = parseTableCellRange(token.value);
3781
+ if (!range) return { value: null, error: "invalid-reference" };
3782
+ for (const label of getTableCellRangeLabels(range)) {
3783
+ if (name === "COUNT") {
3784
+ const cellValue2 = this.readOptionalCellNumber(label);
3785
+ if (cellValue2 != null) values.push(cellValue2);
3786
+ continue;
3787
+ }
3788
+ const cellValue = this.readOptionalCellNumber(label);
3789
+ if (cellValue != null) values.push(cellValue);
3790
+ }
3791
+ } else if (token.type === "cell") {
3792
+ this.index += 1;
3793
+ const cellValue = this.readOptionalCellNumber(token.value);
3794
+ if (cellValue != null) values.push(cellValue);
3795
+ } else {
3796
+ const value = this.parseExpression();
3797
+ if (value.error) return value;
3798
+ values.push(value.value);
3799
+ }
3800
+ if (this.consumeComma()) {
3801
+ continue;
3802
+ }
3803
+ if (this.consumeParen(")")) {
3804
+ break;
3805
+ }
3806
+ return { value: null, error: "invalid-formula" };
3807
+ }
3808
+ if (name === "SUM") return toFiniteFormulaResult(values.reduce((sum, value) => sum + value, 0));
3809
+ if (name === "AVG") {
3810
+ return values.length > 0 ? toFiniteFormulaResult(values.reduce((sum, value) => sum + value, 0) / values.length) : { value: null, error: "division-by-zero" };
3811
+ }
3812
+ if (name === "MIN") return toFiniteFormulaResult(values.length > 0 ? Math.min(...values) : 0);
3813
+ if (name === "MAX") return toFiniteFormulaResult(values.length > 0 ? Math.max(...values) : 0);
3814
+ if (name === "COUNT") return { value: values.length, error: null };
3815
+ return { value: null, error: "invalid-formula" };
3816
+ }
3817
+ readCellNumber(label) {
3818
+ const address = parseTableCellAddress(label);
3819
+ const parsed = address ? parseTableCellNumericValue(this.getCellValue(address.label)) : null;
3820
+ if (parsed == null) {
3821
+ return { value: null, error: "invalid-reference" };
3822
+ }
3823
+ return { value: parsed, error: null };
3824
+ }
3825
+ readOptionalCellNumber(label) {
3826
+ const address = parseTableCellAddress(label);
3827
+ return address ? parseTableCellNumericValue(this.getCellValue(address.label)) : null;
3828
+ }
3829
+ peekOperator(operators) {
3830
+ const token = this.tokens[this.index];
3831
+ return token?.type === "operator" && operators.includes(token.value) ? token : null;
3832
+ }
3833
+ consumeComma() {
3834
+ if (this.tokens[this.index]?.type !== "comma") {
3835
+ return false;
3836
+ }
3837
+ this.index += 1;
3838
+ return true;
3839
+ }
3840
+ consumeParen(value) {
3841
+ const token = this.tokens[this.index];
3842
+ if (token?.type !== "paren" || token.value !== value) {
3843
+ return false;
3844
+ }
3845
+ this.index += 1;
3846
+ return true;
3847
+ }
3848
+ };
3849
+
3850
+ // src/components/UEditor/table-formula-references.ts
3851
+ var CELL_REFERENCE_RE = /(^|[^A-Z0-9_])(\$?)([A-Z]+)(\$?)([1-9]\d*)\b/gi;
3852
+ function rewriteTableFormulaReferences(formula, mapCoordinate) {
3853
+ return formula.replace(CELL_REFERENCE_RE, (_match, prefix, columnMarker, columnName, rowMarker, rowNumber) => {
3854
+ const column = columnNameToIndex(columnName);
3855
+ const row = Number.parseInt(rowNumber, 10) - 1;
3856
+ if (column < 0 || row < 0) return _match;
3857
+ const mapped = mapCoordinate({
3858
+ column,
3859
+ row,
3860
+ columnAbsolute: columnMarker === "$",
3861
+ rowAbsolute: rowMarker === "$"
3862
+ });
3863
+ if (!mapped) return `${prefix}#REF!`;
3864
+ return `${prefix}${columnMarker}${indexToColumnName(mapped.column)}${rowMarker}${mapped.row + 1}`;
3865
+ });
3866
+ }
3867
+ function rewriteNodeFormulas(node, mapCoordinate) {
3868
+ if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
3869
+ const formula = typeof node.attrs.formula === "string" ? node.attrs.formula : "";
3870
+ if (!formula) return node;
3871
+ const nextFormula = rewriteTableFormulaReferences(formula, mapCoordinate);
3872
+ if (nextFormula === formula) return node;
3873
+ return node.type.create({ ...node.attrs, formula: nextFormula, computedValue: null }, node.content, node.marks);
3874
+ }
3875
+ if (node.childCount === 0) return node;
3876
+ const children = [];
3877
+ let changed = false;
3878
+ node.forEach((child) => {
3879
+ const nextChild = rewriteNodeFormulas(child, mapCoordinate);
3880
+ children.push(nextChild);
3881
+ if (nextChild !== child) changed = true;
3882
+ });
3883
+ return changed ? node.type.create(node.attrs, children, node.marks) : node;
3884
+ }
3885
+ function rewriteTableNodeFormulaReferences(tableNode, mapCoordinate) {
3886
+ return rewriteNodeFormulas(tableNode, mapCoordinate);
3887
+ }
3888
+ function rewriteTableNodeFormulaReferencesForInsertion(tableNode, axis, index, count = 1) {
3889
+ return rewriteTableNodeFormulaReferences(tableNode, (coordinate) => {
3890
+ const current = coordinate[axis];
3891
+ return current >= index ? { ...coordinate, [axis]: current + count } : coordinate;
3892
+ });
3893
+ }
3894
+ function rewriteTableNodeFormulaReferencesForDeletion(tableNode, axis, index, count = 1) {
3895
+ const end = index + count;
3896
+ return rewriteTableNodeFormulaReferences(tableNode, (coordinate) => {
3897
+ const current = coordinate[axis];
3898
+ if (current >= index && current < end) return null;
3899
+ return current >= end ? { ...coordinate, [axis]: current - count } : coordinate;
3900
+ });
3901
+ }
3902
+ function rewriteTableNodeFormulaReferencesForMove(tableNode, axis, from, to) {
3903
+ if (from === to) return tableNode;
3904
+ return rewriteTableNodeFormulaReferences(tableNode, (coordinate) => {
3905
+ const current = coordinate[axis];
3906
+ if (current === from) return { ...coordinate, [axis]: to };
3907
+ if (from < to && current > from && current <= to) {
3908
+ return { ...coordinate, [axis]: current - 1 };
3909
+ }
3910
+ if (from > to && current >= to && current < from) {
3911
+ return { ...coordinate, [axis]: current + 1 };
3912
+ }
3913
+ return coordinate;
3914
+ });
3915
+ }
3916
+ function shiftCopiedTableNodeFormulaReferences(node, axis, amount) {
3917
+ return rewriteNodeFormulas(node, (coordinate) => {
3918
+ const isAbsolute = axis === "column" ? coordinate.columnAbsolute : coordinate.rowAbsolute;
3919
+ return {
3920
+ ...coordinate,
3921
+ [axis]: isAbsolute ? coordinate[axis] : Math.max(0, coordinate[axis] + amount)
3922
+ };
3923
+ });
3924
+ }
3925
+
3926
+ // src/components/UEditor/table-cell-commands.ts
3023
3927
  function getCellSelectionPositions(selection) {
3024
3928
  const value = selection;
3025
3929
  const anchor = value.$anchorCell?.pos;
@@ -3062,7 +3966,11 @@ function collectChildren(node) {
3062
3966
  return children;
3063
3967
  }
3064
3968
  function createEmptyCellNode(cellNode) {
3065
- return cellNode.type.createAndFill(cellNode.attrs) ?? cellNode;
3969
+ return cellNode.type.createAndFill({
3970
+ ...cellNode.attrs,
3971
+ formula: null,
3972
+ computedValue: null
3973
+ }) ?? cellNode;
3066
3974
  }
3067
3975
  function createCellCopyForColumnDuplicate(cellNode) {
3068
3976
  return cellNode.type.create(cellNode.attrs, cellNode.content);
@@ -3222,11 +4130,16 @@ function replaceTableAtCellPos(editor, cellPos, updateTable) {
3222
4130
  }
3223
4131
  function duplicateTableRowAt(editor, rowIndex, cellPos) {
3224
4132
  return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
3225
- const rows = collectChildren(tableNode);
4133
+ const tableWithShiftedReferences = rewriteTableNodeFormulaReferencesForInsertion(
4134
+ tableNode,
4135
+ "row",
4136
+ rowIndex + 1
4137
+ );
4138
+ const rows = collectChildren(tableWithShiftedReferences);
3226
4139
  const rowNode = rows[rowIndex];
3227
4140
  if (!rowNode) return null;
3228
- rows.splice(rowIndex + 1, 0, rowNode.copy(rowNode.content));
3229
- return tableNode.type.create(tableNode.attrs, rows);
4141
+ rows.splice(rowIndex + 1, 0, shiftCopiedTableNodeFormulaReferences(rowNode, "row", 1));
4142
+ return tableWithShiftedReferences.type.create(tableWithShiftedReferences.attrs, rows);
3230
4143
  });
3231
4144
  }
3232
4145
  function clearTableRowAt(editor, rowIndex, cellPos) {
@@ -3247,9 +4160,14 @@ function clearTableRowAt(editor, rowIndex, cellPos) {
3247
4160
  }
3248
4161
  function duplicateTableColumnAt(editor, columnIndex, cellPos) {
3249
4162
  return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
3250
- const map = TableMap.get(tableNode);
4163
+ const tableWithShiftedReferences = rewriteTableNodeFormulaReferencesForInsertion(
4164
+ tableNode,
4165
+ "column",
4166
+ columnIndex + 1
4167
+ );
4168
+ const map = TableMap.get(tableWithShiftedReferences);
3251
4169
  if (columnIndex < 0 || columnIndex >= map.width) return null;
3252
- const rows = getTableRows(tableNode).map((rowInfo, rowIndex) => {
4170
+ const rows = getTableRows(tableWithShiftedReferences).map((rowInfo, rowIndex) => {
3253
4171
  const cells = collectChildren(rowInfo.node);
3254
4172
  const sourceCell = rowInfo.cells.find((entry) => {
3255
4173
  const rect = safeFindCell(map, entry.relativePos);
@@ -3261,10 +4179,31 @@ function duplicateTableColumnAt(editor, columnIndex, cellPos) {
3261
4179
  cells[sourceCell.index] = createCellWithDuplicatedLogicalColumn(sourceCell.node, columnIndex - sourceRect.left);
3262
4180
  return rowInfo.node.type.create(rowInfo.node.attrs, cells);
3263
4181
  }
3264
- cells.splice(sourceCell.index + 1, 0, createCellCopyForColumnDuplicate(sourceCell.node));
4182
+ cells.splice(
4183
+ sourceCell.index + 1,
4184
+ 0,
4185
+ shiftCopiedTableNodeFormulaReferences(createCellCopyForColumnDuplicate(sourceCell.node), "column", 1)
4186
+ );
3265
4187
  return rowInfo.node.type.create(rowInfo.node.attrs, cells);
3266
4188
  });
3267
- return tableNode.type.create(tableNode.attrs, rows);
4189
+ const responsiveLayout = normalizeTableWidthMode(tableWithShiftedReferences.attrs.widthMode) === "responsive" ? insertResponsiveColumnLayout(
4190
+ getResponsiveTableWidthBp(tableWithShiftedReferences),
4191
+ getTableColumnRatios(tableWithShiftedReferences),
4192
+ columnIndex + 1,
4193
+ columnIndex
4194
+ ) : null;
4195
+ const tableAttrs = responsiveLayout ? {
4196
+ ...tableWithShiftedReferences.attrs,
4197
+ widthMode: "responsive",
4198
+ widthBp: responsiveLayout.widthBp,
4199
+ offsetBp: resolveResponsiveTableOffsetBp(
4200
+ responsiveLayout.widthBp,
4201
+ tableWithShiftedReferences.attrs.textAlign,
4202
+ tableWithShiftedReferences.attrs.offsetBp
4203
+ ),
4204
+ columnRatios: responsiveLayout.columnRatios
4205
+ } : tableWithShiftedReferences.attrs;
4206
+ return tableWithShiftedReferences.type.create(tableAttrs, rows);
3268
4207
  });
3269
4208
  }
3270
4209
  function clearTableColumnAt(editor, columnIndex, cellPos) {
@@ -3500,14 +4439,6 @@ function useSharedEditorUiRenderState(editor) {
3500
4439
  }
3501
4440
  return context.value;
3502
4441
  }
3503
- function fileToDataUrl2(file) {
3504
- return new Promise((resolve, reject) => {
3505
- const reader = new FileReader();
3506
- reader.onload = () => resolve(String(reader.result ?? ""));
3507
- reader.onerror = () => reject(reader.error ?? new Error("Failed to read image file"));
3508
- reader.readAsDataURL(file);
3509
- });
3510
- }
3511
4442
  function formatTableInsertLabel(template, rows, cols) {
3512
4443
  return template.replace("{rows}", String(rows)).replace("{cols}", String(cols));
3513
4444
  }
@@ -3553,7 +4484,7 @@ var TableInsertGrid = ({
3553
4484
  const maxCols = 8;
3554
4485
  return /* @__PURE__ */ jsxs5("div", { className: "mb-2 rounded-xl border border-border/60 bg-muted/20 p-2", children: [
3555
4486
  /* @__PURE__ */ jsx7("div", { className: "mb-2 text-sm font-medium text-foreground", children: formatTableInsertLabel(previewTemplate, selection.rows, selection.cols) }),
3556
- /* @__PURE__ */ jsx7("div", { className: "grid grid-cols-8 gap-1", children: Array.from({ length: maxRows }).map(
4487
+ /* @__PURE__ */ jsx7("div", { className: "grid grid-cols-8 gap-1", role: "grid", "aria-label": insertLabel, children: Array.from({ length: maxRows }).map(
3557
4488
  (_, rowIndex) => Array.from({ length: maxCols }).map((__, colIndex) => {
3558
4489
  const rows = rowIndex + 1;
3559
4490
  const cols = colIndex + 1;
@@ -3562,10 +4493,22 @@ var TableInsertGrid = ({
3562
4493
  "button",
3563
4494
  {
3564
4495
  type: "button",
4496
+ role: "gridcell",
4497
+ tabIndex: rows === selection.rows && cols === selection.cols ? 0 : -1,
4498
+ "aria-selected": rows === selection.rows && cols === selection.cols,
4499
+ "data-table-grid-cell": `${rows}-${cols}`,
3565
4500
  "aria-label": formatTableInsertLabel(previewTemplate, rows, cols),
3566
4501
  onMouseDown: (e) => e.preventDefault(),
3567
4502
  onMouseEnter: () => setSelection({ rows, cols }),
3568
4503
  onFocus: () => setSelection({ rows, cols }),
4504
+ onKeyDown: (event) => {
4505
+ const nextRows = event.key === "ArrowUp" ? Math.max(1, rows - 1) : event.key === "ArrowDown" ? Math.min(maxRows, rows + 1) : rows;
4506
+ const nextCols = event.key === "ArrowLeft" ? Math.max(1, cols - 1) : event.key === "ArrowRight" ? Math.min(maxCols, cols + 1) : cols;
4507
+ if (nextRows === rows && nextCols === cols) return;
4508
+ event.preventDefault();
4509
+ setSelection({ rows: nextRows, cols: nextCols });
4510
+ event.currentTarget.parentElement?.querySelector(`[data-table-grid-cell="${nextRows}-${nextCols}"]`)?.focus();
4511
+ },
3569
4512
  onClick: () => onInsert(rows, cols),
3570
4513
  className: cn(
3571
4514
  "h-5 w-5 rounded-sm border transition-colors",
@@ -3597,6 +4540,8 @@ var EditorToolbar = ({
3597
4540
  imageInsertMode = "base64",
3598
4541
  maxImageFileSize = DEFAULT_UEDITOR_IMAGE_MAX_FILE_SIZE,
3599
4542
  allowedImageMimeTypes = DEFAULT_UEDITOR_IMAGE_MIME_TYPES,
4543
+ onImageUploadError,
4544
+ fallbackToDataUrl = true,
3600
4545
  fontFamilies,
3601
4546
  defaultFontFamily = "Inter",
3602
4547
  fontSizes,
@@ -3655,28 +4600,36 @@ var EditorToolbar = ({
3655
4600
  const applyFontSizeDraft = () => {
3656
4601
  const parsed = Number.parseFloat(fontSizeDraft);
3657
4602
  if (!Number.isFinite(parsed)) return;
3658
- editor.chain().focus().setFontSize(`${parsed}px`).run();
3659
- setFontSizeDraft(String(parsed));
4603
+ const clamped = Math.min(512, Math.max(8, parsed));
4604
+ editor.chain().focus().setFontSize(`${clamped}px`).run();
4605
+ setFontSizeDraft(String(clamped));
3660
4606
  };
3661
4607
  const insertImageFiles = async (files) => {
3662
4608
  if (files.length === 0) return;
3663
4609
  setIsUploadingImage(true);
3664
4610
  setImageUploadError(null);
3665
- for (const file of files) {
3666
- if (!file.type.startsWith("image/")) continue;
3667
- if (file.size > maxImageFileSize) continue;
3668
- if (allowedImageMimeTypes.length > 0 && !allowedImageMimeTypes.includes(file.type)) continue;
3669
- try {
3670
- const src = imageInsertMode === "upload" && uploadImage ? await uploadImage(file) : await fileToDataUrl2(file);
3671
- const safeSrc = sanitizeUEditorUrl(src, "image");
3672
- if (!safeSrc) continue;
3673
- editor.chain().focus().setImage({ src: safeSrc, alt: file.name }).run();
3674
- editor.commands.createParagraphNear();
3675
- } catch {
3676
- setImageUploadError(t("imageInput.uploadError"));
4611
+ const trackedPosition = trackEditorInsertionPosition(editor);
4612
+ try {
4613
+ const { images, hadError } = await resolveUEditorImageFiles(files, {
4614
+ maxFileSize: maxImageFileSize,
4615
+ allowedMimeTypes: allowedImageMimeTypes,
4616
+ upload: uploadImage,
4617
+ fallbackToDataUrl,
4618
+ insertMode: imageInsertMode,
4619
+ onError: onImageUploadError
4620
+ });
4621
+ if (hadError) setImageUploadError(t("imageInput.uploadError"));
4622
+ if (!editor.isDestroyed && images.length > 0) {
4623
+ const content = images.map((image) => ({
4624
+ type: "image",
4625
+ attrs: { src: image.src, alt: image.file.name }
4626
+ }));
4627
+ editor.commands.insertContentAt(trackedPosition.current, content, { updateSelection: false });
3677
4628
  }
4629
+ } finally {
4630
+ trackedPosition.stop();
4631
+ setIsUploadingImage(false);
3678
4632
  }
3679
- setIsUploadingImage(false);
3680
4633
  };
3681
4634
  if (variant === "minimal") {
3682
4635
  return /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-0.5 border-b border-border/35 bg-muted/30 p-1.5", children: [
@@ -3831,6 +4784,8 @@ var EditorToolbar = ({
3831
4784
  "input",
3832
4785
  {
3833
4786
  type: "number",
4787
+ min: 8,
4788
+ max: 512,
3834
4789
  step: 1,
3835
4790
  value: isEditingFontSize ? fontSizeDraft : displayedFontSizeLabel,
3836
4791
  onFocus: () => {
@@ -4380,7 +5335,7 @@ var EditorToolbar = ({
4380
5335
  {
4381
5336
  ref: fileInputRef,
4382
5337
  type: "file",
4383
- accept: "image/*",
5338
+ accept: allowedImageMimeTypes.length > 0 ? allowedImageMimeTypes.join(",") : "image/*",
4384
5339
  multiple: true,
4385
5340
  className: "hidden",
4386
5341
  onChange: (e) => {
@@ -4769,6 +5724,25 @@ export {
4769
5724
  DropdownMenu_default,
4770
5725
  isSafeUEditorUrl,
4771
5726
  sanitizeUEditorUrl,
5727
+ TABLE_WIDTH_BASIS_POINTS,
5728
+ DEFAULT_RESPONSIVE_TABLE_WIDTH_BP,
5729
+ clampResponsiveTableWidthBp,
5730
+ normalizeTableWidthMode,
5731
+ parsePercentageToBasisPoints,
5732
+ formatBasisPointsAsPercentage,
5733
+ parseColumnRatios,
5734
+ normalizeColumnRatios,
5735
+ getLogicalTableColumnCount,
5736
+ getLegacyTableColumnWeights,
5737
+ getTableColumnRatios,
5738
+ getResponsiveTableWidthBp,
5739
+ getResponsiveTableOffsetBp,
5740
+ resolveResponsiveTableOffsetBp,
5741
+ moveResponsiveColumnRatio,
5742
+ insertResponsiveColumnLayout,
5743
+ deleteResponsiveColumnLayout,
5744
+ trackEditorInsertionPosition,
5745
+ resolveUEditorImageFiles,
4772
5746
  DEFAULT_UEDITOR_IMAGE_MAX_FILE_SIZE,
4773
5747
  DEFAULT_UEDITOR_IMAGE_MIME_TYPES,
4774
5748
  ClipboardImages,
@@ -4796,6 +5770,21 @@ export {
4796
5770
  getRelativeSelectedCellsMetrics,
4797
5771
  findTableNodeInfoFromState,
4798
5772
  applyTableAlignment,
5773
+ indexToColumnName,
5774
+ parseTableCellRange,
5775
+ getTableCellRangeLabels,
5776
+ normalizeTableFormula,
5777
+ isDraftTableFormula,
5778
+ formatFormulaError,
5779
+ formatTableFormulaDisplayValue,
5780
+ getTableFormulaReferences,
5781
+ buildTableFormulaDependencyGraph,
5782
+ getTableFormulaRecalculationOrder,
5783
+ getAffectedTableFormulaLabels,
5784
+ evaluateBasicTableFormula,
5785
+ rewriteTableNodeFormulaReferencesForInsertion,
5786
+ rewriteTableNodeFormulaReferencesForDeletion,
5787
+ rewriteTableNodeFormulaReferencesForMove,
4799
5788
  dispatchTableLayoutChange,
4800
5789
  mergeTableCellsPreservingColumnWidths,
4801
5790
  runTableCommandAtCellPos,
@@ -4823,10 +5812,9 @@ export {
4823
5812
  getTableAnchorPos,
4824
5813
  EditorUiRenderStateProvider,
4825
5814
  useSharedEditorUiRenderState,
4826
- fileToDataUrl2 as fileToDataUrl,
4827
5815
  ToolbarButton,
4828
5816
  TableInsertGrid,
4829
5817
  EditorToolbar,
4830
5818
  UEDITOR_PROSEMIRROR_CLASS_NAME
4831
5819
  };
4832
- //# sourceMappingURL=chunk-KTOXCD6J.js.map
5820
+ //# sourceMappingURL=chunk-37MHVJMV.js.map