@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.
- package/api-reference.json +1 -1
- package/dist/{EmojiPicker-NV3GQYWI.js → EmojiPicker-ISJ6RXYG.js} +4 -4
- package/dist/{chunk-LK6YWBI6.js → chunk-2P7TSXEE.js} +82 -25
- package/dist/chunk-2P7TSXEE.js.map +1 -0
- package/dist/{chunk-5QWYO527.js → chunk-37MHVJMV.js} +846 -90
- package/dist/chunk-37MHVJMV.js.map +1 -0
- package/dist/{chunk-XTZTUDRE.js → chunk-57BKIC6J.js} +711 -800
- package/dist/chunk-57BKIC6J.js.map +1 -0
- package/dist/{chunk-LRIRY4DG.js → chunk-5W63IHIN.js} +11 -3
- package/dist/chunk-5W63IHIN.js.map +1 -0
- package/dist/{chunk-F7MX3XXK.js → chunk-S4UHFRL3.js} +3 -3
- package/dist/{chunk-A4GUPKHM.js → chunk-ZP7F4ZD7.js} +63 -43
- package/dist/chunk-ZP7F4ZD7.js.map +1 -0
- package/dist/index.cjs +2225 -1311
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +23 -10
- package/dist/index.js.map +1 -1
- package/dist/{menu-bar-AHG64PEB.js → menu-bar-DPCNNUX5.js} +193 -47
- package/dist/menu-bar-DPCNNUX5.js.map +1 -0
- package/dist/ueditor.cjs +9407 -8506
- package/dist/ueditor.cjs.map +1 -1
- package/dist/ueditor.d.cts +10 -0
- package/dist/ueditor.d.ts +10 -0
- package/dist/ueditor.js +4 -4
- package/package.json +2 -1
- package/dist/chunk-5QWYO527.js.map +0 -1
- package/dist/chunk-A4GUPKHM.js.map +0 -1
- package/dist/chunk-LK6YWBI6.js.map +0 -1
- package/dist/chunk-LRIRY4DG.js.map +0 -1
- package/dist/chunk-XTZTUDRE.js.map +0 -1
- package/dist/menu-bar-AHG64PEB.js.map +0 -1
- /package/dist/{EmojiPicker-NV3GQYWI.js.map → EmojiPicker-ISJ6RXYG.js.map} +0 -0
- /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-
|
|
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
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
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
|
-
|
|
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) =>
|
|
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(
|
|
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
|
|
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,
|
|
3427
|
-
return
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
3466
|
-
getResponsiveTableWidthBp(
|
|
3467
|
-
getTableColumnRatios(
|
|
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
|
-
...
|
|
4196
|
+
...tableWithShiftedReferences.attrs,
|
|
3473
4197
|
widthMode: "responsive",
|
|
3474
4198
|
widthBp: responsiveLayout.widthBp,
|
|
3475
4199
|
offsetBp: resolveResponsiveTableOffsetBp(
|
|
3476
4200
|
responsiveLayout.widthBp,
|
|
3477
|
-
|
|
3478
|
-
|
|
4201
|
+
tableWithShiftedReferences.attrs.textAlign,
|
|
4202
|
+
tableWithShiftedReferences.attrs.offsetBp
|
|
3479
4203
|
),
|
|
3480
4204
|
columnRatios: responsiveLayout.columnRatios
|
|
3481
|
-
} :
|
|
3482
|
-
return
|
|
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
|
-
|
|
3874
|
-
|
|
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
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
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-
|
|
5820
|
+
//# sourceMappingURL=chunk-37MHVJMV.js.map
|