@underverse-ui/underverse 2.0.29 → 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-LK6YWBI6.js → chunk-2P7TSXEE.js} +82 -25
  4. package/dist/chunk-2P7TSXEE.js.map +1 -0
  5. package/dist/{chunk-5QWYO527.js → chunk-37MHVJMV.js} +846 -90
  6. package/dist/chunk-37MHVJMV.js.map +1 -0
  7. package/dist/{chunk-XTZTUDRE.js → chunk-57BKIC6J.js} +711 -800
  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 +2225 -1311
  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-AHG64PEB.js → menu-bar-DPCNNUX5.js} +193 -47
  21. package/dist/menu-bar-DPCNNUX5.js.map +1 -0
  22. package/dist/ueditor.cjs +9407 -8506
  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-5QWYO527.js.map +0 -1
  29. package/dist/chunk-A4GUPKHM.js.map +0 -1
  30. package/dist/chunk-LK6YWBI6.js.map +0 -1
  31. package/dist/chunk-LRIRY4DG.js.map +0 -1
  32. package/dist/chunk-XTZTUDRE.js.map +0 -1
  33. package/dist/menu-bar-AHG64PEB.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,6 +1202,87 @@ 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";
@@ -2138,26 +2227,6 @@ function getImageFiles(dataTransfer) {
2138
2227
  }
2139
2228
  return Array.from(byKey.values());
2140
2229
  }
2141
- function fileToDataUrl(file) {
2142
- return new Promise((resolve, reject) => {
2143
- const reader = new FileReader();
2144
- reader.onload = () => resolve(String(reader.result ?? ""));
2145
- reader.onerror = () => reject(reader.error ?? new Error("Failed to read image file"));
2146
- reader.readAsDataURL(file);
2147
- });
2148
- }
2149
- async function resolveImageSrc(file, options) {
2150
- if (options.insertMode === "upload" && options.upload) {
2151
- try {
2152
- const result = await options.upload(file);
2153
- const src = typeof result === "string" ? sanitizeUEditorUrl(result, "image") : "";
2154
- if (src) return src;
2155
- } catch (err) {
2156
- if (!options.fallbackToDataUrl) throw err;
2157
- }
2158
- }
2159
- return fileToDataUrl(file);
2160
- }
2161
2230
  var ClipboardImages = Extension.create({
2162
2231
  name: "clipboardImages",
2163
2232
  addOptions() {
@@ -2166,25 +2235,32 @@ var ClipboardImages = Extension.create({
2166
2235
  allowedMimeTypes: DEFAULT_UEDITOR_IMAGE_MIME_TYPES,
2167
2236
  upload: void 0,
2168
2237
  fallbackToDataUrl: true,
2169
- insertMode: "base64"
2238
+ insertMode: "base64",
2239
+ onError: void 0
2170
2240
  };
2171
2241
  },
2172
2242
  addProseMirrorPlugins() {
2173
2243
  const editor = this.editor;
2174
2244
  const options = this.options;
2175
2245
  const insertFiles = async (files, selectionPos) => {
2176
- if (selectionPos !== void 0) {
2177
- editor.commands.setTextSelection(selectionPos);
2178
- }
2179
- for (const file of files) {
2180
- if (file.size > options.maxFileSize) continue;
2181
- if (options.allowedMimeTypes.length > 0 && !options.allowedMimeTypes.includes(file.type)) continue;
2182
- try {
2183
- const src = await resolveImageSrc(file, options);
2184
- editor.chain().focus().setImage({ src, alt: file.name }).run();
2185
- editor.commands.createParagraphNear();
2186
- } catch {
2187
- }
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();
2188
2264
  }
2189
2265
  };
2190
2266
  return [
@@ -2213,7 +2289,8 @@ var ClipboardImages = Extension.create({
2213
2289
  },
2214
2290
  handleDrop: (view, event, _slice, moved) => {
2215
2291
  if (moved) return false;
2216
- if (!(event instanceof DragEvent)) return false;
2292
+ const DragEventCtor = view.dom.ownerDocument.defaultView?.DragEvent;
2293
+ if (!DragEventCtor || !(event instanceof DragEventCtor)) return false;
2217
2294
  const files = getImageFiles(event.dataTransfer);
2218
2295
  if (files.length === 0) return false;
2219
2296
  const pos = view.posAtCoords({ left: event.clientX, top: event.clientY })?.pos;
@@ -2243,6 +2320,7 @@ var LinkInput = ({
2243
2320
  const [url, setUrl] = useState3(initialUrl);
2244
2321
  const [error, setError] = useState3("");
2245
2322
  const inputRef = useRef2(null);
2323
+ const inputId = useId();
2246
2324
  const errorId = useId();
2247
2325
  useEffect3(() => {
2248
2326
  inputRef.current?.focus();
@@ -2259,12 +2337,17 @@ var LinkInput = ({
2259
2337
  onSubmit(normalized);
2260
2338
  };
2261
2339
  return /* @__PURE__ */ jsxs3("form", { onSubmit: handleSubmit, className: "p-2", children: [
2340
+ /* @__PURE__ */ jsx4("label", { htmlFor: inputId, className: "sr-only", children: t("toolbar.link") }),
2262
2341
  /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
2263
2342
  /* @__PURE__ */ jsx4(
2264
2343
  "input",
2265
2344
  {
2266
2345
  ref: inputRef,
2346
+ id: inputId,
2267
2347
  type: "text",
2348
+ inputMode: "url",
2349
+ name: "link-url",
2350
+ autoComplete: "off",
2268
2351
  value: url,
2269
2352
  onChange: (e) => {
2270
2353
  setUrl(e.target.value);
@@ -2276,8 +2359,8 @@ var LinkInput = ({
2276
2359
  className: `flex-1 rounded-lg bg-muted/50 px-3 py-2 text-sm ${formControlOutlineClass} aria-invalid:border-destructive`
2277
2360
  }
2278
2361
  ),
2279
- /* @__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" }) }),
2280
- /* @__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" }) })
2281
2364
  ] }),
2282
2365
  error ? /* @__PURE__ */ jsx4("p", { id: errorId, role: "alert", className: "mt-1.5 px-1 text-xs text-destructive", children: error }) : null
2283
2366
  ] });
@@ -2286,7 +2369,11 @@ var ImageInput = ({ onSubmit, onCancel }) => {
2286
2369
  const t = useSmartTranslations("UEditor");
2287
2370
  const [url, setUrl] = useState3("");
2288
2371
  const [alt, setAlt] = useState3("");
2372
+ const [error, setError] = useState3("");
2289
2373
  const inputRef = useRef2(null);
2374
+ const urlId = useId();
2375
+ const altId = useId();
2376
+ const errorId = useId();
2290
2377
  useEffect3(() => {
2291
2378
  inputRef.current?.focus();
2292
2379
  }, []);
@@ -2294,30 +2381,45 @@ var ImageInput = ({ onSubmit, onCancel }) => {
2294
2381
  e.preventDefault();
2295
2382
  const safeUrl = sanitizeUEditorUrl(url, "image");
2296
2383
  if (safeUrl) {
2384
+ setError("");
2297
2385
  onSubmit(safeUrl, alt);
2386
+ } else {
2387
+ setError(t("imageInput.invalid"));
2298
2388
  }
2299
2389
  };
2300
2390
  return /* @__PURE__ */ jsxs3("form", { onSubmit: handleSubmit, className: "p-3 space-y-3", children: [
2301
2391
  /* @__PURE__ */ jsxs3("div", { children: [
2302
- /* @__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") }),
2303
2393
  /* @__PURE__ */ jsx4(
2304
2394
  "input",
2305
2395
  {
2306
2396
  ref: inputRef,
2397
+ id: urlId,
2307
2398
  type: "text",
2399
+ inputMode: "url",
2400
+ name: "image-url",
2401
+ autoComplete: "off",
2308
2402
  value: url,
2309
- onChange: (e) => setUrl(e.target.value),
2403
+ onChange: (e) => {
2404
+ setUrl(e.target.value);
2405
+ if (error) setError("");
2406
+ },
2310
2407
  placeholder: t("imageInput.urlPlaceholder"),
2408
+ "aria-invalid": Boolean(error),
2409
+ "aria-describedby": error ? errorId : void 0,
2311
2410
  className: `mt-1 w-full rounded-lg bg-muted/50 px-3 py-2 text-sm ${formControlOutlineClass}`
2312
2411
  }
2313
2412
  )
2314
2413
  ] }),
2315
2414
  /* @__PURE__ */ jsxs3("div", { children: [
2316
- /* @__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") }),
2317
2416
  /* @__PURE__ */ jsx4(
2318
2417
  "input",
2319
2418
  {
2419
+ id: altId,
2320
2420
  type: "text",
2421
+ name: "image-alt",
2422
+ autoComplete: "off",
2321
2423
  value: alt,
2322
2424
  onChange: (e) => setAlt(e.target.value),
2323
2425
  placeholder: t("imageInput.altPlaceholder"),
@@ -2325,6 +2427,7 @@ var ImageInput = ({ onSubmit, onCancel }) => {
2325
2427
  }
2326
2428
  )
2327
2429
  ] }),
2430
+ error ? /* @__PURE__ */ jsx4("p", { id: errorId, role: "alert", className: "text-xs text-destructive", children: error }) : null,
2328
2431
  /* @__PURE__ */ jsxs3("div", { className: "flex gap-2", children: [
2329
2432
  /* @__PURE__ */ jsx4(
2330
2433
  "button",
@@ -3218,6 +3321,609 @@ function applyTableAlignment(editor, tableAlign, anchorPos) {
3218
3321
  // src/components/UEditor/table-cell-commands.ts
3219
3322
  import { TextSelection as TextSelection3 } from "@tiptap/pm/state";
3220
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
3221
3927
  function getCellSelectionPositions(selection) {
3222
3928
  const value = selection;
3223
3929
  const anchor = value.$anchorCell?.pos;
@@ -3260,7 +3966,11 @@ function collectChildren(node) {
3260
3966
  return children;
3261
3967
  }
3262
3968
  function createEmptyCellNode(cellNode) {
3263
- return cellNode.type.createAndFill(cellNode.attrs) ?? cellNode;
3969
+ return cellNode.type.createAndFill({
3970
+ ...cellNode.attrs,
3971
+ formula: null,
3972
+ computedValue: null
3973
+ }) ?? cellNode;
3264
3974
  }
3265
3975
  function createCellCopyForColumnDuplicate(cellNode) {
3266
3976
  return cellNode.type.create(cellNode.attrs, cellNode.content);
@@ -3420,11 +4130,16 @@ function replaceTableAtCellPos(editor, cellPos, updateTable) {
3420
4130
  }
3421
4131
  function duplicateTableRowAt(editor, rowIndex, cellPos) {
3422
4132
  return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
3423
- const rows = collectChildren(tableNode);
4133
+ const tableWithShiftedReferences = rewriteTableNodeFormulaReferencesForInsertion(
4134
+ tableNode,
4135
+ "row",
4136
+ rowIndex + 1
4137
+ );
4138
+ const rows = collectChildren(tableWithShiftedReferences);
3424
4139
  const rowNode = rows[rowIndex];
3425
4140
  if (!rowNode) return null;
3426
- rows.splice(rowIndex + 1, 0, rowNode.copy(rowNode.content));
3427
- 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);
3428
4143
  });
3429
4144
  }
3430
4145
  function clearTableRowAt(editor, rowIndex, cellPos) {
@@ -3445,9 +4160,14 @@ function clearTableRowAt(editor, rowIndex, cellPos) {
3445
4160
  }
3446
4161
  function duplicateTableColumnAt(editor, columnIndex, cellPos) {
3447
4162
  return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
3448
- const map = TableMap.get(tableNode);
4163
+ const tableWithShiftedReferences = rewriteTableNodeFormulaReferencesForInsertion(
4164
+ tableNode,
4165
+ "column",
4166
+ columnIndex + 1
4167
+ );
4168
+ const map = TableMap.get(tableWithShiftedReferences);
3449
4169
  if (columnIndex < 0 || columnIndex >= map.width) return null;
3450
- const rows = getTableRows(tableNode).map((rowInfo, rowIndex) => {
4170
+ const rows = getTableRows(tableWithShiftedReferences).map((rowInfo, rowIndex) => {
3451
4171
  const cells = collectChildren(rowInfo.node);
3452
4172
  const sourceCell = rowInfo.cells.find((entry) => {
3453
4173
  const rect = safeFindCell(map, entry.relativePos);
@@ -3459,27 +4179,31 @@ function duplicateTableColumnAt(editor, columnIndex, cellPos) {
3459
4179
  cells[sourceCell.index] = createCellWithDuplicatedLogicalColumn(sourceCell.node, columnIndex - sourceRect.left);
3460
4180
  return rowInfo.node.type.create(rowInfo.node.attrs, cells);
3461
4181
  }
3462
- 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
+ );
3463
4187
  return rowInfo.node.type.create(rowInfo.node.attrs, cells);
3464
4188
  });
3465
- const responsiveLayout = normalizeTableWidthMode(tableNode.attrs.widthMode) === "responsive" ? insertResponsiveColumnLayout(
3466
- getResponsiveTableWidthBp(tableNode),
3467
- getTableColumnRatios(tableNode),
4189
+ const responsiveLayout = normalizeTableWidthMode(tableWithShiftedReferences.attrs.widthMode) === "responsive" ? insertResponsiveColumnLayout(
4190
+ getResponsiveTableWidthBp(tableWithShiftedReferences),
4191
+ getTableColumnRatios(tableWithShiftedReferences),
3468
4192
  columnIndex + 1,
3469
4193
  columnIndex
3470
4194
  ) : null;
3471
4195
  const tableAttrs = responsiveLayout ? {
3472
- ...tableNode.attrs,
4196
+ ...tableWithShiftedReferences.attrs,
3473
4197
  widthMode: "responsive",
3474
4198
  widthBp: responsiveLayout.widthBp,
3475
4199
  offsetBp: resolveResponsiveTableOffsetBp(
3476
4200
  responsiveLayout.widthBp,
3477
- tableNode.attrs.textAlign,
3478
- tableNode.attrs.offsetBp
4201
+ tableWithShiftedReferences.attrs.textAlign,
4202
+ tableWithShiftedReferences.attrs.offsetBp
3479
4203
  ),
3480
4204
  columnRatios: responsiveLayout.columnRatios
3481
- } : tableNode.attrs;
3482
- return tableNode.type.create(tableAttrs, rows);
4205
+ } : tableWithShiftedReferences.attrs;
4206
+ return tableWithShiftedReferences.type.create(tableAttrs, rows);
3483
4207
  });
3484
4208
  }
3485
4209
  function clearTableColumnAt(editor, columnIndex, cellPos) {
@@ -3715,14 +4439,6 @@ function useSharedEditorUiRenderState(editor) {
3715
4439
  }
3716
4440
  return context.value;
3717
4441
  }
3718
- function fileToDataUrl2(file) {
3719
- return new Promise((resolve, reject) => {
3720
- const reader = new FileReader();
3721
- reader.onload = () => resolve(String(reader.result ?? ""));
3722
- reader.onerror = () => reject(reader.error ?? new Error("Failed to read image file"));
3723
- reader.readAsDataURL(file);
3724
- });
3725
- }
3726
4442
  function formatTableInsertLabel(template, rows, cols) {
3727
4443
  return template.replace("{rows}", String(rows)).replace("{cols}", String(cols));
3728
4444
  }
@@ -3768,7 +4484,7 @@ var TableInsertGrid = ({
3768
4484
  const maxCols = 8;
3769
4485
  return /* @__PURE__ */ jsxs5("div", { className: "mb-2 rounded-xl border border-border/60 bg-muted/20 p-2", children: [
3770
4486
  /* @__PURE__ */ jsx7("div", { className: "mb-2 text-sm font-medium text-foreground", children: formatTableInsertLabel(previewTemplate, selection.rows, selection.cols) }),
3771
- /* @__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(
3772
4488
  (_, rowIndex) => Array.from({ length: maxCols }).map((__, colIndex) => {
3773
4489
  const rows = rowIndex + 1;
3774
4490
  const cols = colIndex + 1;
@@ -3777,10 +4493,22 @@ var TableInsertGrid = ({
3777
4493
  "button",
3778
4494
  {
3779
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}`,
3780
4500
  "aria-label": formatTableInsertLabel(previewTemplate, rows, cols),
3781
4501
  onMouseDown: (e) => e.preventDefault(),
3782
4502
  onMouseEnter: () => setSelection({ rows, cols }),
3783
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
+ },
3784
4512
  onClick: () => onInsert(rows, cols),
3785
4513
  className: cn(
3786
4514
  "h-5 w-5 rounded-sm border transition-colors",
@@ -3812,6 +4540,8 @@ var EditorToolbar = ({
3812
4540
  imageInsertMode = "base64",
3813
4541
  maxImageFileSize = DEFAULT_UEDITOR_IMAGE_MAX_FILE_SIZE,
3814
4542
  allowedImageMimeTypes = DEFAULT_UEDITOR_IMAGE_MIME_TYPES,
4543
+ onImageUploadError,
4544
+ fallbackToDataUrl = true,
3815
4545
  fontFamilies,
3816
4546
  defaultFontFamily = "Inter",
3817
4547
  fontSizes,
@@ -3870,28 +4600,36 @@ var EditorToolbar = ({
3870
4600
  const applyFontSizeDraft = () => {
3871
4601
  const parsed = Number.parseFloat(fontSizeDraft);
3872
4602
  if (!Number.isFinite(parsed)) return;
3873
- editor.chain().focus().setFontSize(`${parsed}px`).run();
3874
- 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));
3875
4606
  };
3876
4607
  const insertImageFiles = async (files) => {
3877
4608
  if (files.length === 0) return;
3878
4609
  setIsUploadingImage(true);
3879
4610
  setImageUploadError(null);
3880
- for (const file of files) {
3881
- if (!file.type.startsWith("image/")) continue;
3882
- if (file.size > maxImageFileSize) continue;
3883
- if (allowedImageMimeTypes.length > 0 && !allowedImageMimeTypes.includes(file.type)) continue;
3884
- try {
3885
- const src = imageInsertMode === "upload" && uploadImage ? await uploadImage(file) : await fileToDataUrl2(file);
3886
- const safeSrc = sanitizeUEditorUrl(src, "image");
3887
- if (!safeSrc) continue;
3888
- editor.chain().focus().setImage({ src: safeSrc, alt: file.name }).run();
3889
- editor.commands.createParagraphNear();
3890
- } catch {
3891
- 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 });
3892
4628
  }
4629
+ } finally {
4630
+ trackedPosition.stop();
4631
+ setIsUploadingImage(false);
3893
4632
  }
3894
- setIsUploadingImage(false);
3895
4633
  };
3896
4634
  if (variant === "minimal") {
3897
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: [
@@ -4046,6 +4784,8 @@ var EditorToolbar = ({
4046
4784
  "input",
4047
4785
  {
4048
4786
  type: "number",
4787
+ min: 8,
4788
+ max: 512,
4049
4789
  step: 1,
4050
4790
  value: isEditingFontSize ? fontSizeDraft : displayedFontSizeLabel,
4051
4791
  onFocus: () => {
@@ -4595,7 +5335,7 @@ var EditorToolbar = ({
4595
5335
  {
4596
5336
  ref: fileInputRef,
4597
5337
  type: "file",
4598
- accept: "image/*",
5338
+ accept: allowedImageMimeTypes.length > 0 ? allowedImageMimeTypes.join(",") : "image/*",
4599
5339
  multiple: true,
4600
5340
  className: "hidden",
4601
5341
  onChange: (e) => {
@@ -5001,6 +5741,8 @@ export {
5001
5741
  moveResponsiveColumnRatio,
5002
5742
  insertResponsiveColumnLayout,
5003
5743
  deleteResponsiveColumnLayout,
5744
+ trackEditorInsertionPosition,
5745
+ resolveUEditorImageFiles,
5004
5746
  DEFAULT_UEDITOR_IMAGE_MAX_FILE_SIZE,
5005
5747
  DEFAULT_UEDITOR_IMAGE_MIME_TYPES,
5006
5748
  ClipboardImages,
@@ -5028,6 +5770,21 @@ export {
5028
5770
  getRelativeSelectedCellsMetrics,
5029
5771
  findTableNodeInfoFromState,
5030
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,
5031
5788
  dispatchTableLayoutChange,
5032
5789
  mergeTableCellsPreservingColumnWidths,
5033
5790
  runTableCommandAtCellPos,
@@ -5055,10 +5812,9 @@ export {
5055
5812
  getTableAnchorPos,
5056
5813
  EditorUiRenderStateProvider,
5057
5814
  useSharedEditorUiRenderState,
5058
- fileToDataUrl2 as fileToDataUrl,
5059
5815
  ToolbarButton,
5060
5816
  TableInsertGrid,
5061
5817
  EditorToolbar,
5062
5818
  UEDITOR_PROSEMIRROR_CLASS_NAME
5063
5819
  };
5064
- //# sourceMappingURL=chunk-5QWYO527.js.map
5820
+ //# sourceMappingURL=chunk-37MHVJMV.js.map