@underverse-ui/underverse 2.0.28 → 2.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/api-reference.json +1 -1
  2. package/dist/{EmojiPicker-NV3GQYWI.js → EmojiPicker-ISJ6RXYG.js} +4 -4
  3. package/dist/{chunk-Y4X6CAZY.js → chunk-2P7TSXEE.js} +82 -25
  4. package/dist/chunk-2P7TSXEE.js.map +1 -0
  5. package/dist/{chunk-KTOXCD6J.js → chunk-37MHVJMV.js} +1078 -90
  6. package/dist/chunk-37MHVJMV.js.map +1 -0
  7. package/dist/{chunk-QYFXYDBK.js → chunk-57BKIC6J.js} +1306 -888
  8. package/dist/chunk-57BKIC6J.js.map +1 -0
  9. package/dist/{chunk-LRIRY4DG.js → chunk-5W63IHIN.js} +11 -3
  10. package/dist/chunk-5W63IHIN.js.map +1 -0
  11. package/dist/{chunk-F7MX3XXK.js → chunk-S4UHFRL3.js} +3 -3
  12. package/dist/{chunk-A4GUPKHM.js → chunk-ZP7F4ZD7.js} +63 -43
  13. package/dist/chunk-ZP7F4ZD7.js.map +1 -0
  14. package/dist/index.cjs +3275 -1628
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +55 -0
  17. package/dist/index.d.ts +55 -0
  18. package/dist/index.js +23 -10
  19. package/dist/index.js.map +1 -1
  20. package/dist/{menu-bar-CFIWMBAA.js → menu-bar-DPCNNUX5.js} +227 -54
  21. package/dist/menu-bar-DPCNNUX5.js.map +1 -0
  22. package/dist/ueditor.cjs +9803 -8169
  23. package/dist/ueditor.cjs.map +1 -1
  24. package/dist/ueditor.d.cts +10 -0
  25. package/dist/ueditor.d.ts +10 -0
  26. package/dist/ueditor.js +4 -4
  27. package/package.json +2 -1
  28. package/dist/chunk-A4GUPKHM.js.map +0 -1
  29. package/dist/chunk-KTOXCD6J.js.map +0 -1
  30. package/dist/chunk-LRIRY4DG.js.map +0 -1
  31. package/dist/chunk-QYFXYDBK.js.map +0 -1
  32. package/dist/chunk-Y4X6CAZY.js.map +0 -1
  33. package/dist/menu-bar-CFIWMBAA.js.map +0 -1
  34. /package/dist/{EmojiPicker-NV3GQYWI.js.map → EmojiPicker-ISJ6RXYG.js.map} +0 -0
  35. /package/dist/{chunk-F7MX3XXK.js.map → chunk-S4UHFRL3.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  Modal_default
4
- } from "./chunk-Y4X6CAZY.js";
4
+ } from "./chunk-2P7TSXEE.js";
5
5
  import {
6
6
  DEFAULT_TABLE_ROW_HEIGHT,
7
7
  DEFAULT_UEDITOR_IMAGE_MAX_FILE_SIZE,
@@ -13,23 +13,33 @@ import {
13
13
  ImageInput,
14
14
  LinkInput,
15
15
  MIN_TABLE_ROW_HEIGHT,
16
+ TABLE_WIDTH_BASIS_POINTS,
16
17
  TableInsertGrid,
17
18
  UEDITOR_PROSEMIRROR_CLASS_NAME,
18
19
  applyEditorLink,
19
- fileToDataUrl,
20
+ clampResponsiveTableWidthBp,
21
+ formatBasisPointsAsPercentage,
20
22
  getTableAnchorPos,
23
+ normalizeColumnRatios,
24
+ parseColumnRatios,
25
+ parsePercentageToBasisPoints,
26
+ resolveResponsiveTableOffsetBp,
27
+ resolveUEditorImageFiles,
21
28
  sanitizeUEditorUrl,
29
+ trackEditorInsertionPosition,
22
30
  useDropdownMenuClose,
23
31
  useSharedEditorUiRenderState
24
- } from "./chunk-KTOXCD6J.js";
32
+ } from "./chunk-37MHVJMV.js";
25
33
  import {
26
34
  cn,
27
- useSmartTranslations
28
- } from "./chunk-A4GUPKHM.js";
35
+ resolveUEditorPortalContainer,
36
+ useSmartTranslations,
37
+ useUEditorPortalContainerGetter
38
+ } from "./chunk-ZP7F4ZD7.js";
29
39
  import "./chunk-NSBPE2FW.js";
30
40
 
31
41
  // src/components/UEditor/menu-bar.tsx
32
- import React, { useMemo, useRef, useState } from "react";
42
+ import React, { useEffect, useRef, useState } from "react";
33
43
  import {
34
44
  AlignCenter,
35
45
  AlignJustify,
@@ -64,6 +74,68 @@ import {
64
74
  } from "lucide-react";
65
75
 
66
76
  // src/components/UEditor/preview-html.ts
77
+ var BLOCKED_PREVIEW_ELEMENTS = /* @__PURE__ */ new Set(["SCRIPT", "IFRAME", "OBJECT", "EMBED", "LINK", "META", "BASE", "STYLE"]);
78
+ var UNSAFE_INLINE_STYLE_RE = /(?:url\s*\(|expression\s*\(|@import|-moz-binding)/i;
79
+ function isSafeUEditorSourceHtml(html, ownerDocument) {
80
+ const sourceDocument = ownerDocument ?? (typeof document !== "undefined" ? document : null);
81
+ if (!sourceDocument) return false;
82
+ const container = sourceDocument.createElement("div");
83
+ container.innerHTML = html;
84
+ for (const element of Array.from(container.querySelectorAll("*"))) {
85
+ if (BLOCKED_PREVIEW_ELEMENTS.has(element.tagName)) return false;
86
+ for (const attribute of Array.from(element.attributes)) {
87
+ const name = attribute.name.toLowerCase();
88
+ if (name.startsWith("on") || name === "srcdoc") return false;
89
+ }
90
+ const style = element.getAttribute("style");
91
+ if (style && UNSAFE_INLINE_STYLE_RE.test(style)) return false;
92
+ const isFileCard = element.getAttribute("data-type") === "file-card";
93
+ const urlAttributes = [
94
+ ["href", isFileCard ? "file" : "link"],
95
+ ["src", "image"],
96
+ ["data-src", isFileCard ? "file" : "image"],
97
+ ["data-url", "link"],
98
+ ["data-image", "image"]
99
+ ];
100
+ for (const [name, kind] of urlAttributes) {
101
+ if (element.hasAttribute(name) && !sanitizeUEditorUrl(element.getAttribute(name) ?? "", kind)) return false;
102
+ }
103
+ }
104
+ return true;
105
+ }
106
+ function sanitizePreviewContainer(container) {
107
+ for (const element of Array.from(container.querySelectorAll("*"))) {
108
+ if (BLOCKED_PREVIEW_ELEMENTS.has(element.tagName)) {
109
+ element.remove();
110
+ continue;
111
+ }
112
+ for (const attribute of Array.from(element.attributes)) {
113
+ const name = attribute.name.toLowerCase();
114
+ if (name.startsWith("on") || name === "srcdoc") {
115
+ element.removeAttribute(attribute.name);
116
+ }
117
+ }
118
+ const style = element.getAttribute("style");
119
+ if (style && UNSAFE_INLINE_STYLE_RE.test(style)) element.removeAttribute("style");
120
+ const isFileCard = element.getAttribute("data-type") === "file-card";
121
+ const urlAttributes = [
122
+ ["href", isFileCard ? "file" : "link"],
123
+ ["src", "image"],
124
+ ["data-src", isFileCard ? "file" : "image"],
125
+ ["data-url", "link"],
126
+ ["data-image", "image"]
127
+ ];
128
+ for (const [name, kind] of urlAttributes) {
129
+ if (!element.hasAttribute(name)) continue;
130
+ const safeUrl = sanitizeUEditorUrl(element.getAttribute(name) ?? "", kind);
131
+ if (safeUrl) element.setAttribute(name, safeUrl);
132
+ else element.removeAttribute(name);
133
+ }
134
+ if (element.tagName === "A" && element.getAttribute("target") === "_blank") {
135
+ element.setAttribute("rel", "noopener noreferrer");
136
+ }
137
+ }
138
+ }
67
139
  var DEFAULT_TABLE_COLUMN_WIDTH = 100;
68
140
  var TIPTAP_TABLE_MIN_COLUMN_WIDTH = 25;
69
141
  function parsePixelWidth(value) {
@@ -154,6 +226,17 @@ function normalizePreviewRowHeight(row) {
154
226
  function normalizePreviewTable(table) {
155
227
  const widths = resolveColumnWidths(table);
156
228
  if (widths.length === 0) return;
229
+ const storedMode = table.getAttribute("data-table-width-mode");
230
+ const responsive = storedMode === "responsive" || storedMode === "full" || parsePercentageToBasisPoints(table.style.width) !== null;
231
+ const storedRatios = parseColumnRatios(table.getAttribute("data-table-column-ratios"));
232
+ const columnRatios = normalizeColumnRatios(
233
+ storedRatios?.length === widths.length ? storedRatios : widths,
234
+ widths.length
235
+ );
236
+ const widthBp = responsive ? Number(table.getAttribute("data-table-width-bp")) || parsePercentageToBasisPoints(table.getAttribute("data-table-width") ?? table.style.width) || TABLE_WIDTH_BASIS_POINTS : TABLE_WIDTH_BASIS_POINTS;
237
+ const safeWidthBp = clampResponsiveTableWidthBp(widthBp);
238
+ const offsetBp = responsive ? Number(table.getAttribute("data-table-offset-bp")) || parsePercentageToBasisPoints(table.getAttribute("data-table-offset") ?? table.style.marginLeft) || 0 : 0;
239
+ const safeOffsetBp = resolveResponsiveTableOffsetBp(safeWidthBp, null, offsetBp);
157
240
  let colgroup = table.querySelector("colgroup");
158
241
  if (!colgroup) {
159
242
  colgroup = document.createElement("colgroup");
@@ -162,6 +245,7 @@ function normalizePreviewTable(table) {
162
245
  while (colgroup.children.length < widths.length) {
163
246
  colgroup.appendChild(document.createElement("col"));
164
247
  }
248
+ const tableWidth = widths.reduce((sum, width) => sum + width, 0);
165
249
  Array.from(colgroup.children).forEach((child, index) => {
166
250
  if (child.tagName.toLowerCase() !== "col") return;
167
251
  const col = child;
@@ -169,32 +253,42 @@ function normalizePreviewTable(table) {
169
253
  child.remove();
170
254
  return;
171
255
  }
172
- col.style.width = `${widths[index]}px`;
173
- col.style.minWidth = `${widths[index]}px`;
256
+ col.style.width = responsive ? formatBasisPointsAsPercentage(columnRatios[index]) : `${widths[index]}px`;
257
+ col.style.minWidth = responsive ? "" : `${widths[index]}px`;
174
258
  col.setAttribute("width", String(widths[index]));
175
259
  });
176
- const tableWidth = widths.reduce((sum, width) => sum + width, 0);
177
- setStyleProperty(table, "width", `${tableWidth}px`);
178
- setStyleProperty(table, "min-width", `${tableWidth}px`);
260
+ setStyleProperty(table, "width", responsive ? formatBasisPointsAsPercentage(safeWidthBp) : `${tableWidth}px`);
261
+ setStyleProperty(table, "min-width", responsive ? `${widths.length * TIPTAP_TABLE_MIN_COLUMN_WIDTH}px` : `${tableWidth}px`);
179
262
  setStyleProperty(table, "table-layout", "fixed");
263
+ if (responsive) {
264
+ table.setAttribute("data-table-width-mode", "responsive");
265
+ table.setAttribute("data-table-width-bp", String(safeWidthBp));
266
+ table.setAttribute("data-table-offset-bp", String(safeOffsetBp));
267
+ table.setAttribute("data-table-column-ratios", columnRatios.join(","));
268
+ setStyleProperty(table, "margin-left", formatBasisPointsAsPercentage(safeOffsetBp));
269
+ setStyleProperty(table, "margin-right", "auto");
270
+ }
180
271
  Array.from(table.rows).forEach((row) => {
181
272
  let columnIndex = 0;
182
273
  normalizePreviewRowHeight(row);
183
274
  Array.from(row.cells).forEach((cell) => {
184
275
  const colspan = getCellColspan(cell);
185
276
  const cellWidth = widths.slice(columnIndex, columnIndex + colspan).reduce((sum, width) => sum + width, 0);
277
+ const cellRatio = columnRatios.slice(columnIndex, columnIndex + colspan).reduce((sum, ratio) => sum + ratio, 0);
186
278
  if (cellWidth > 0) {
187
- cell.style.width = `${cellWidth}px`;
188
- cell.style.minWidth = `${cellWidth}px`;
279
+ cell.style.width = responsive ? formatBasisPointsAsPercentage(cellRatio) : `${cellWidth}px`;
280
+ cell.style.minWidth = responsive ? "" : `${cellWidth}px`;
189
281
  }
190
282
  columnIndex += colspan;
191
283
  });
192
284
  });
193
285
  }
194
- function prepareUEditorPreviewHtml(html) {
195
- if (typeof document === "undefined" || !html) return html;
196
- const container = document.createElement("div");
286
+ function prepareUEditorPreviewHtml(html, ownerDocument) {
287
+ const previewDocument = ownerDocument ?? (typeof document !== "undefined" ? document : null);
288
+ if (!previewDocument || !html) return html;
289
+ const container = previewDocument.createElement("div");
197
290
  container.innerHTML = html;
291
+ sanitizePreviewContainer(container);
198
292
  container.querySelectorAll("table").forEach((table) => normalizePreviewTable(table));
199
293
  return container.innerHTML;
200
294
  }
@@ -262,13 +356,17 @@ function buildFileMenuItems(t, editor, { onSave, onExport }) {
262
356
  onExport();
263
357
  } else {
264
358
  const html = editor.getHTML();
265
- const blob = new Blob([`<!DOCTYPE html><html><body>${html}</body></html>`], { type: "text/html" });
266
- const url = URL.createObjectURL(blob);
267
- const a = document.createElement("a");
359
+ const editorDocument = editor.view.dom.ownerDocument;
360
+ const editorWindow = editorDocument.defaultView;
361
+ const BlobCtor = editorWindow?.Blob ?? Blob;
362
+ const URLApi = editorWindow?.URL ?? URL;
363
+ const blob = new BlobCtor([`<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>${html}</body></html>`], { type: "text/html;charset=utf-8" });
364
+ const url = URLApi.createObjectURL(blob);
365
+ const a = editorDocument.createElement("a");
268
366
  a.href = url;
269
367
  a.download = "document.html";
270
368
  a.click();
271
- URL.revokeObjectURL(url);
369
+ editorWindow?.setTimeout(() => URLApi.revokeObjectURL(url), 0);
272
370
  }
273
371
  }
274
372
  }
@@ -298,29 +396,31 @@ function buildEditMenuItems(t, editor) {
298
396
  label: t("menubar.cut"),
299
397
  icon: Scissors,
300
398
  shortcut: t("menubar.cutShortcut"),
301
- onClick: () => document.execCommand("cut")
399
+ onClick: () => editor.view.dom.ownerDocument.execCommand("cut")
302
400
  },
303
401
  {
304
402
  type: "action",
305
403
  label: t("menubar.copy"),
306
404
  shortcut: t("menubar.copyShortcut"),
307
- onClick: () => document.execCommand("copy")
405
+ onClick: () => editor.view.dom.ownerDocument.execCommand("copy")
308
406
  },
309
407
  {
310
408
  type: "action",
311
409
  label: t("menubar.paste"),
312
410
  shortcut: t("menubar.pasteShortcut"),
313
- onClick: () => document.execCommand("paste")
411
+ onClick: () => editor.view.dom.ownerDocument.execCommand("paste")
314
412
  },
315
413
  {
316
414
  type: "action",
317
415
  label: t("menubar.pasteAsText"),
318
416
  onClick: async () => {
319
417
  try {
320
- const text = await navigator.clipboard.readText();
418
+ const clipboard = editor.view.dom.ownerDocument.defaultView?.navigator.clipboard;
419
+ if (!clipboard) throw new Error("Clipboard API is unavailable");
420
+ const text = await clipboard.readText();
321
421
  editor.chain().focus().insertContent(text).run();
322
422
  } catch {
323
- document.execCommand("paste");
423
+ editor.view.dom.ownerDocument.execCommand("paste");
324
424
  }
325
425
  }
326
426
  },
@@ -333,7 +433,7 @@ function buildEditMenuItems(t, editor) {
333
433
  }
334
434
  ];
335
435
  }
336
- function buildViewMenuItems(t, {
436
+ function buildViewMenuItems(t, editor, {
337
437
  containerRef,
338
438
  onSourceCode,
339
439
  openSourceDialog,
@@ -361,10 +461,11 @@ function buildViewMenuItems(t, {
361
461
  onClick: () => {
362
462
  const el = containerRef.current;
363
463
  if (!el) return;
364
- if (!document.fullscreenElement) {
464
+ const editorDocument = editor.view.dom.ownerDocument;
465
+ if (!editorDocument.fullscreenElement) {
365
466
  el.requestFullscreen?.();
366
467
  } else {
367
- document.exitFullscreen?.();
468
+ editorDocument.exitFullscreen?.();
368
469
  }
369
470
  }
370
471
  }
@@ -643,6 +744,8 @@ var MenuBar = ({
643
744
  imageInsertMode = "base64",
644
745
  maxImageFileSize = DEFAULT_UEDITOR_IMAGE_MAX_FILE_SIZE,
645
746
  allowedImageMimeTypes = DEFAULT_UEDITOR_IMAGE_MIME_TYPES,
747
+ onImageUploadError,
748
+ fallbackToDataUrl = true,
646
749
  onSave,
647
750
  onExport,
648
751
  onSourceCode,
@@ -651,22 +754,45 @@ var MenuBar = ({
651
754
  }) => {
652
755
  const t = useSmartTranslations("UEditor");
653
756
  useSharedEditorUiRenderState(editor);
757
+ const getPortalContainer = useUEditorPortalContainerGetter();
654
758
  const fileInputRef = useRef(null);
759
+ const sourceTextareaRef = useRef(null);
655
760
  const [showImageInput, setShowImageInput] = useState(false);
656
761
  const [showLinkInput, setShowLinkInput] = useState(false);
762
+ const [isUploadingImage, setIsUploadingImage] = useState(false);
763
+ const [imageUploadError, setImageUploadError] = useState(null);
657
764
  const [openMenuKey, setOpenMenuKey] = useState(null);
658
765
  const [showSourceDialog, setShowSourceDialog] = useState(false);
659
766
  const [sourceHtml, setSourceHtml] = useState("");
767
+ const [sourceError, setSourceError] = useState("");
768
+ const sourceErrorId = React.useId();
660
769
  const [showPreviewDialog, setShowPreviewDialog] = useState(false);
661
- const previewHtml = useMemo(
662
- () => showPreviewDialog ? prepareUEditorPreviewHtml(editor.getHTML()) : "",
663
- [editor, showPreviewDialog]
770
+ const [previewHtml, setPreviewHtml] = useState("");
771
+ const modalPortalContainer = resolveUEditorPortalContainer(
772
+ getPortalContainer,
773
+ void 0,
774
+ editor.view.dom.ownerDocument
664
775
  );
776
+ useEffect(() => {
777
+ if (!showPreviewDialog) return;
778
+ const updatePreview = () => {
779
+ if (!editor.isDestroyed) {
780
+ setPreviewHtml(prepareUEditorPreviewHtml(editor.getHTML(), editor.view.dom.ownerDocument));
781
+ }
782
+ };
783
+ updatePreview();
784
+ editor.on("update", updatePreview);
785
+ return () => {
786
+ editor.off("update", updatePreview);
787
+ };
788
+ }, [editor, showPreviewDialog]);
665
789
  const openSourceDialog = () => {
666
790
  setSourceHtml(editor.getHTML());
791
+ setSourceError("");
667
792
  setShowSourceDialog(true);
668
793
  };
669
794
  const openPreviewDialog = () => {
795
+ setPreviewHtml(prepareUEditorPreviewHtml(editor.getHTML(), editor.view.dom.ownerDocument));
670
796
  setShowPreviewDialog(true);
671
797
  };
672
798
  const handlePreview = () => {
@@ -674,13 +800,23 @@ var MenuBar = ({
674
800
  openPreviewDialog();
675
801
  };
676
802
  const applySourceHtml = () => {
677
- editor.chain().focus().setContent(sourceHtml).run();
678
- setShowSourceDialog(false);
803
+ const nextSourceHtml = sourceTextareaRef.current?.value ?? sourceHtml;
804
+ try {
805
+ if (!isSafeUEditorSourceHtml(nextSourceHtml, editor.view.dom.ownerDocument)) {
806
+ throw new Error("Unsafe source HTML");
807
+ }
808
+ editor.chain().focus().setContent(nextSourceHtml, { errorOnInvalidContent: true }).run();
809
+ setSourceError("");
810
+ setShowSourceDialog(false);
811
+ } catch {
812
+ setSourceError(t("menubar.invalidSource"));
813
+ }
679
814
  };
680
815
  const closeInsertMenu = () => {
681
816
  setOpenMenuKey(null);
682
817
  setShowImageInput(false);
683
818
  setShowLinkInput(false);
819
+ setImageUploadError(null);
684
820
  };
685
821
  const handleImageUrl = (url, alt) => {
686
822
  const safe = sanitizeUEditorUrl(url, "image");
@@ -691,21 +827,35 @@ var MenuBar = ({
691
827
  closeInsertMenu();
692
828
  };
693
829
  const handleImageFiles = async (files) => {
694
- if (!files?.length) return;
695
- for (const file of Array.from(files)) {
696
- if (!file.type.startsWith("image/")) continue;
697
- if (file.size > maxImageFileSize) continue;
698
- if (allowedImageMimeTypes.length > 0 && !allowedImageMimeTypes.includes(file.type)) continue;
699
- try {
700
- const src = imageInsertMode === "upload" && uploadImage ? await uploadImage(file) : await fileToDataUrl(file);
701
- const safe = sanitizeUEditorUrl(src, "image");
702
- if (!safe) continue;
703
- editor.chain().focus().setImage({ src: safe, alt: file.name }).run();
704
- editor.commands.createParagraphNear();
705
- } catch {
830
+ if (files.length === 0 || isUploadingImage) return;
831
+ setIsUploadingImage(true);
832
+ setImageUploadError(null);
833
+ const trackedPosition = trackEditorInsertionPosition(editor);
834
+ try {
835
+ const { images, hadError } = await resolveUEditorImageFiles(files, {
836
+ maxFileSize: maxImageFileSize,
837
+ allowedMimeTypes: allowedImageMimeTypes,
838
+ upload: uploadImage,
839
+ fallbackToDataUrl,
840
+ insertMode: imageInsertMode,
841
+ onError: onImageUploadError
842
+ });
843
+ if (!editor.isDestroyed && images.length > 0) {
844
+ const content = images.map((image) => ({
845
+ type: "image",
846
+ attrs: { src: image.src, alt: image.file.name }
847
+ }));
848
+ editor.commands.insertContentAt(trackedPosition.current, content, { updateSelection: false });
849
+ }
850
+ if (hadError) {
851
+ setImageUploadError(t("imageInput.uploadError"));
852
+ } else {
853
+ closeInsertMenu();
706
854
  }
855
+ } finally {
856
+ trackedPosition.stop();
857
+ setIsUploadingImage(false);
707
858
  }
708
- closeInsertMenu();
709
859
  };
710
860
  const handleInsertTable = (rows, cols) => {
711
861
  editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run();
@@ -724,12 +874,14 @@ var MenuBar = ({
724
874
  /* @__PURE__ */ jsx(
725
875
  DropdownMenuItem,
726
876
  {
727
- label: t("menubar.imageUpload"),
877
+ label: isUploadingImage ? t("imageInput.uploading") : t("menubar.imageUpload"),
728
878
  icon: Upload,
729
879
  onClick: () => fileInputRef.current?.click(),
730
- closeOnSelect: false
880
+ closeOnSelect: false,
881
+ disabled: isUploadingImage
731
882
  }
732
- )
883
+ ),
884
+ imageUploadError ? /* @__PURE__ */ jsx(DropdownMenuItem, { label: imageUploadError, disabled: true, destructive: true }) : null
733
885
  ] })
734
886
  },
735
887
  { type: "separator" },
@@ -810,7 +962,13 @@ var MenuBar = ({
810
962
  {
811
963
  key: "view",
812
964
  label: t("menubar.view"),
813
- items: buildViewMenuItems(t, { containerRef, onSourceCode, openSourceDialog, onPreview: handlePreview, openPreviewDialog })
965
+ items: buildViewMenuItems(t, editor, {
966
+ containerRef,
967
+ onSourceCode,
968
+ openSourceDialog,
969
+ onPreview: handlePreview,
970
+ openPreviewDialog
971
+ })
814
972
  },
815
973
  {
816
974
  key: "insert",
@@ -834,10 +992,14 @@ var MenuBar = ({
834
992
  accept: allowedImageMimeTypes.join(","),
835
993
  multiple: true,
836
994
  className: "hidden",
837
- onChange: (e) => handleImageFiles(e.target.files)
995
+ onChange: (e) => {
996
+ const files = Array.from(e.target.files ?? []);
997
+ e.target.value = "";
998
+ void handleImageFiles(files);
999
+ }
838
1000
  }
839
1001
  ),
840
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 border-b border-border/35 bg-muted/20 px-1.5 py-0.5", children: [
1002
+ /* @__PURE__ */ jsxs("nav", { "aria-label": t("toolbar.showMenuBar"), className: "flex items-center gap-0.5 border-b border-border/35 bg-muted/20 px-1.5 py-0.5", children: [
841
1003
  menus.map(({ key, label, items }) => /* @__PURE__ */ jsx(
842
1004
  DropdownMenu,
843
1005
  {
@@ -878,18 +1040,28 @@ var MenuBar = ({
878
1040
  onClose: () => setShowSourceDialog(false),
879
1041
  title: t("menubar.sourceCode"),
880
1042
  size: "lg",
1043
+ portalContainer: modalPortalContainer,
881
1044
  children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
882
1045
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("menubar.sourceCodeHint") }),
883
1046
  /* @__PURE__ */ jsx(
884
1047
  "textarea",
885
1048
  {
1049
+ ref: sourceTextareaRef,
886
1050
  "data-testid": "source-code-textarea",
1051
+ "aria-label": t("menubar.sourceCode"),
1052
+ name: "ueditor-source-html",
887
1053
  className: "w-full h-64 font-mono text-xs p-3 bg-muted rounded-lg border border-border resize-y focus:outline-none focus:ring-2 focus:ring-primary/20",
888
1054
  value: sourceHtml,
889
- onChange: (e) => setSourceHtml(e.target.value),
1055
+ onChange: (e) => {
1056
+ setSourceHtml(e.target.value);
1057
+ if (sourceError) setSourceError("");
1058
+ },
1059
+ "aria-invalid": Boolean(sourceError),
1060
+ "aria-describedby": sourceError ? sourceErrorId : void 0,
890
1061
  spellCheck: false
891
1062
  }
892
1063
  ),
1064
+ sourceError ? /* @__PURE__ */ jsx("p", { id: sourceErrorId, role: "alert", className: "text-xs text-destructive", children: sourceError }) : null,
893
1065
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2 justify-end", children: [
894
1066
  /* @__PURE__ */ jsx(
895
1067
  "button",
@@ -920,6 +1092,7 @@ var MenuBar = ({
920
1092
  onClose: () => setShowPreviewDialog(false),
921
1093
  title: t("menubar.preview"),
922
1094
  size: "full",
1095
+ portalContainer: modalPortalContainer,
923
1096
  width: "min(1180px, calc(100vw - 2rem))",
924
1097
  height: "min(860px, calc(100vh - 2rem))",
925
1098
  className: "flex flex-col overflow-hidden rounded-xl md:rounded-2xl",
@@ -945,4 +1118,4 @@ var MenuBar = ({
945
1118
  export {
946
1119
  MenuBar
947
1120
  };
948
- //# sourceMappingURL=menu-bar-CFIWMBAA.js.map
1121
+ //# sourceMappingURL=menu-bar-DPCNNUX5.js.map