@underverse-ui/underverse 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -4928,6 +4928,9 @@ function getClipboardData(dataTransfer, type) {
4928
4928
  return "";
4929
4929
  }
4930
4930
  }
4931
+ function hasClipboardHtmlTable(dataTransfer) {
4932
+ return /<table(?:\s|>)/i.test(getClipboardData(dataTransfer, "text/html"));
4933
+ }
4931
4934
  function extractClipboardHtmlFragment(html) {
4932
4935
  const startMarker = "<!--StartFragment-->";
4933
4936
  const endMarker = "<!--EndFragment-->";
@@ -4938,6 +4941,13 @@ function extractClipboardHtmlFragment(html) {
4938
4941
  }
4939
4942
  return html;
4940
4943
  }
4944
+ function hasMeaningfulContentOutsideTable(container) {
4945
+ const clone = container.cloneNode(true);
4946
+ clone.querySelectorAll("table, style, script, meta, link").forEach((element) => element.remove());
4947
+ const remainingText = (clone.textContent ?? "").replace(/\u00a0/g, " ").replace(/\u200b/g, "").trim();
4948
+ if (remainingText) return true;
4949
+ return !!clone.querySelector("img, video, audio, iframe, object, embed, svg, canvas, hr, input, textarea, select, button");
4950
+ }
4941
4951
  function normalizeClipboardCellText(value) {
4942
4952
  return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\u00a0/g, " ").replace(/[ \t]+\n/g, "\n").replace(/\n[ \t]+/g, "\n").replace(/\n+$/g, "").replace(/^\n+/g, "").trim();
4943
4953
  }
@@ -5548,7 +5558,10 @@ function getClipboardTableContent(dataTransfer) {
5548
5558
  const fragment = extractClipboardHtmlFragment(html);
5549
5559
  const fragmentDoc = new DOMParser().parseFromString(fragment, "text/html");
5550
5560
  const styleMap = parseClipboardCssClassStyles(doc);
5551
- const table = fragmentDoc.querySelector("table") ?? doc.querySelector("table");
5561
+ const sourceBody = fragmentDoc.querySelector("table") ? fragmentDoc.body : doc.body;
5562
+ const tables = sourceBody.querySelectorAll("table");
5563
+ if (tables.length !== 1 || hasMeaningfulContentOutsideTable(sourceBody)) return null;
5564
+ const table = tables[0];
5552
5565
  if (!(table instanceof HTMLTableElement)) return null;
5553
5566
  return createTableContent(getHtmlTableRows(table, styleMap), 1, {
5554
5567
  backgroundColor: DEFAULT_HTML_TABLE_CELL_BACKGROUND_COLOR
@@ -5727,6 +5740,7 @@ var init_clipboard_images = __esm({
5727
5740
  editor.chain().focus().insertContent(tableContent).run();
5728
5741
  return true;
5729
5742
  }
5743
+ if (hasClipboardHtmlTable(event.clipboardData)) return false;
5730
5744
  const tsvTableContent = getClipboardTsvTableContent(event.clipboardData);
5731
5745
  if (tsvTableContent) {
5732
5746
  event.preventDefault();
@@ -6416,7 +6430,7 @@ var init_colors = __esm({
6416
6430
  "src/components/UEditor/colors.tsx"() {
6417
6431
  "use strict";
6418
6432
  "use client";
6419
- import_react71 = require("react");
6433
+ import_react71 = __toESM(require("react"), 1);
6420
6434
  init_useSmartTranslations();
6421
6435
  import_lucide_react50 = require("lucide-react");
6422
6436
  init_cn();
@@ -6584,12 +6598,25 @@ var init_colors = __esm({
6584
6598
  colors,
6585
6599
  currentColor,
6586
6600
  onSelect,
6601
+ onCustomColorSelect,
6587
6602
  label
6588
6603
  }) => {
6589
6604
  const t = useSmartTranslations("UEditor");
6590
6605
  const colorInputRef = (0, import_react71.useRef)(null);
6591
6606
  const automaticColor = colors[0]?.color ?? "";
6592
6607
  const paletteColors = colors.slice(1);
6608
+ const customColorSelect = onCustomColorSelect ?? onSelect;
6609
+ import_react71.default.useEffect(() => {
6610
+ const input = colorInputRef.current;
6611
+ if (!input) return;
6612
+ const commitColor = () => customColorSelect(input.value);
6613
+ input.addEventListener("change", commitColor);
6614
+ return () => input.removeEventListener("change", commitColor);
6615
+ }, [customColorSelect]);
6616
+ import_react71.default.useEffect(() => {
6617
+ const input = colorInputRef.current;
6618
+ if (input) input.value = currentColor.startsWith("#") ? currentColor : "#000000";
6619
+ }, [currentColor]);
6593
6620
  return /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)("div", { className: "w-56 p-2", children: [
6594
6621
  /* @__PURE__ */ (0, import_jsx_runtime91.jsx)("span", { className: "px-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: label }),
6595
6622
  /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)(
@@ -6643,20 +6670,19 @@ var init_colors = __esm({
6643
6670
  }
6644
6671
  ),
6645
6672
  /* @__PURE__ */ (0, import_jsx_runtime91.jsx)("span", { className: "flex-1 text-center", children: t("colors.moreColors") }),
6646
- /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(import_lucide_react50.Palette, { className: "h-4 w-4 text-muted-foreground" }),
6647
- /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(
6648
- "input",
6649
- {
6650
- ref: colorInputRef,
6651
- type: "color",
6652
- value: currentColor.startsWith("#") ? currentColor : "#000000",
6653
- onChange: (event) => onSelect(event.target.value),
6654
- className: "sr-only",
6655
- tabIndex: -1
6656
- }
6657
- )
6673
+ /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(import_lucide_react50.Palette, { className: "h-4 w-4 text-muted-foreground" })
6658
6674
  ]
6659
6675
  }
6676
+ ),
6677
+ /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(
6678
+ "input",
6679
+ {
6680
+ ref: colorInputRef,
6681
+ type: "color",
6682
+ defaultValue: currentColor.startsWith("#") ? currentColor : "#000000",
6683
+ className: "sr-only",
6684
+ tabIndex: -1
6685
+ }
6660
6686
  )
6661
6687
  ] });
6662
6688
  };
@@ -13684,19 +13710,19 @@ var variantStyles5 = {
13684
13710
  },
13685
13711
  underline: {
13686
13712
  container: "relative border-b border-border/60",
13687
- tab: "relative transition-colors duration-200 pb-3 border-b-2 border-transparent hover:border-primary/30",
13713
+ tab: "relative transition-colors duration-200 border-b-2 border-transparent hover:border-primary/30",
13688
13714
  activeTab: "text-primary border-primary font-semibold",
13689
13715
  inactiveTab: "text-muted-foreground hover:text-foreground"
13690
13716
  },
13691
13717
  card: {
13692
- container: "space-y-1.5 bg-muted/20 p-2 rounded-2xl border border-border/30",
13718
+ container: "bg-muted/20 p-1.5 rounded-2xl border border-border/30",
13693
13719
  tab: "rounded-xl border border-transparent transition-all duration-200",
13694
13720
  activeTab: "bg-primary text-primary-foreground border-primary shadow-md",
13695
13721
  inactiveTab: "text-muted-foreground hover:text-foreground hover:bg-accent/50 hover:border-border/50"
13696
13722
  },
13697
13723
  "underline-card": {
13698
13724
  container: "border-b border-border/60 bg-card/80 backdrop-blur-sm rounded-t-2xl p-1",
13699
- tab: "relative transition-all duration-200 pb-3 px-4 rounded-t-xl border-b-2 border-transparent hover:border-primary/30 hover:bg-accent/30",
13725
+ tab: "relative transition-all duration-200 rounded-t-xl border-b-2 border-transparent hover:border-primary/30 hover:bg-accent/30",
13700
13726
  activeTab: "text-primary border-primary font-semibold bg-accent/30",
13701
13727
  inactiveTab: "text-muted-foreground hover:text-foreground"
13702
13728
  }
@@ -13806,7 +13832,7 @@ var Tabs = ({
13806
13832
  }, [baseId, tabs]);
13807
13833
  const containerClasses = cn(
13808
13834
  "relative",
13809
- orientation === "horizontal" ? "w-full flex space-x-1 overflow-x-auto" : "flex flex-col space-y-1 shrink-0",
13835
+ orientation === "horizontal" ? "w-full flex items-center space-x-1 overflow-x-auto" : "flex flex-col space-y-1 shrink-0",
13810
13836
  variantStyles5[variant].container,
13811
13837
  className
13812
13838
  );
@@ -13841,10 +13867,7 @@ var Tabs = ({
13841
13867
  tab.href && tab.disabled && "pointer-events-none cursor-not-allowed opacity-50"
13842
13868
  );
13843
13869
  const sharedStyle = {
13844
- boxShadow: "none",
13845
- transform: "none",
13846
- outline: "none",
13847
- border: "none"
13870
+ outline: "none"
13848
13871
  };
13849
13872
  const sharedProps = {
13850
13873
  ref: (el) => {
@@ -24800,7 +24823,7 @@ var MultiCombobox = ({
24800
24823
  );
24801
24824
  }),
24802
24825
  hiddenCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: cn("inline-flex items-center bg-muted text-muted-foreground rounded-lg shrink-0", sizeStyles8[size].tag), children: gi18n.moreCount ? gi18n.moreCount(hiddenCount) : `+${hiddenCount} more` })
24803
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: cn(formControlValueClass, "font-medium"), children: gi18n.selectedCount ? gi18n.selectedCount(value.length) : `${value.length} item${value.length > 1 ? "s" : ""} selected` }) : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: cn(formControlValueClass, "text-muted-foreground"), children: placeholder }) }),
24826
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: cn(formControlValueClass, "font-medium"), children: gi18n.selectedCount ? gi18n.selectedCount(value.length) : `${value.length} item${value.length > 1 ? "s" : ""} selected` }) : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: cn(formControlValueClass, "text-left text-muted-foreground"), children: placeholder }) }),
24804
24827
  /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "flex items-center gap-1.5 shrink-0", children: [
24805
24828
  showClear && value.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
24806
24829
  "div",
@@ -38601,15 +38624,18 @@ var FloatingMenuContent = ({ editor }) => {
38601
38624
  }
38602
38625
  );
38603
38626
  };
38604
- function applyTableCellBackground(editor, color) {
38627
+ function applyTableCellBackground(editor, color, options = {}) {
38628
+ const shouldFocus = options.focus ?? true;
38605
38629
  const value = color || null;
38606
38630
  const { state, view } = editor;
38607
38631
  const applied = (0, import_tables8.setCellAttr)("backgroundColor", value)(state, view.dispatch.bind(view));
38608
38632
  if (applied) {
38609
- view.focus();
38633
+ if (shouldFocus) view.focus();
38610
38634
  return;
38611
38635
  }
38612
- editor.chain().focus().setCellAttribute("backgroundColor", value).run();
38636
+ const chain = editor.chain();
38637
+ if (shouldFocus) chain.focus();
38638
+ chain.setCellAttribute("backgroundColor", value).run();
38613
38639
  }
38614
38640
  function applyTableCellAttribute2(editor, name, value, options = {}) {
38615
38641
  const shouldFocus = options.focus ?? true;
@@ -38667,6 +38693,30 @@ function BorderPositionPreviewIcon({ position }) {
38667
38693
  showVertical && /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("path", { d: "M10 2V18", strokeWidth: "2" })
38668
38694
  ] });
38669
38695
  }
38696
+ function getBubbleMenuPosition({
38697
+ start,
38698
+ end,
38699
+ menuSize,
38700
+ viewport,
38701
+ offset = 16,
38702
+ padding = 8
38703
+ }) {
38704
+ const selectionTop = Math.min(start.top, end.top);
38705
+ const selectionBottom = Math.max(start.bottom, end.bottom);
38706
+ const spaceAbove = selectionTop - offset - padding;
38707
+ const spaceBelow = viewport.height - padding - selectionBottom - offset;
38708
+ const placement = menuSize.height <= spaceAbove || spaceAbove >= spaceBelow ? "top" : "bottom";
38709
+ const halfWidth = menuSize.width / 2;
38710
+ const minLeft = padding + halfWidth;
38711
+ const maxLeft = viewport.width - padding - halfWidth;
38712
+ const selectionCenter = (start.left + end.left) / 2;
38713
+ const left = maxLeft >= minLeft ? Math.min(maxLeft, Math.max(minLeft, selectionCenter)) : viewport.width / 2;
38714
+ const requestedTop = placement === "top" ? selectionTop - offset : selectionBottom + offset;
38715
+ const minTop = placement === "top" ? padding + menuSize.height : padding;
38716
+ const maxTop = placement === "top" ? viewport.height - padding : viewport.height - padding - menuSize.height;
38717
+ const top = maxTop >= minTop ? Math.min(maxTop, Math.max(minTop, requestedTop)) : Math.max(padding, requestedTop);
38718
+ return { top, left, placement };
38719
+ }
38670
38720
  function getBubbleMenuContext(editor) {
38671
38721
  const { selection } = editor.state;
38672
38722
  if (editor.isActive("image")) return "image";
@@ -38832,24 +38882,34 @@ var BubbleMenuContent = ({
38832
38882
  setActiveColorPalette(null);
38833
38883
  onKeepOpenChange?.(false);
38834
38884
  }, [onKeepOpenChange]);
38835
- const applyInlineColorAndClose = (0, import_react75.useCallback)((color) => {
38885
+ const applyInlineColor = (0, import_react75.useCallback)((color, focus) => {
38836
38886
  if (activeColorPalette === "text") {
38887
+ const chain = editor.chain();
38888
+ if (focus) chain.focus();
38837
38889
  if (color === "inherit") {
38838
- editor.chain().focus().unsetColor().run();
38890
+ chain.unsetColor().run();
38839
38891
  } else {
38840
- editor.chain().focus().setColor(color).run();
38892
+ chain.setColor(color).run();
38841
38893
  }
38842
38894
  } else if (activeColorPalette === "highlight") {
38895
+ const chain = editor.chain();
38896
+ if (focus) chain.focus();
38843
38897
  if (color === "") {
38844
- editor.chain().focus().unsetHighlight().run();
38898
+ chain.unsetHighlight().run();
38845
38899
  } else {
38846
- editor.chain().focus().toggleHighlight({ color }).run();
38900
+ chain.toggleHighlight({ color }).run();
38847
38901
  }
38848
38902
  } else {
38849
- applyTableCellBackground(editor, color);
38903
+ applyTableCellBackground(editor, color, { focus });
38850
38904
  }
38905
+ }, [activeColorPalette, editor]);
38906
+ const applyInlineColorAndClose = (0, import_react75.useCallback)((color) => {
38907
+ applyInlineColor(color, true);
38851
38908
  closeColorPalette();
38852
- }, [activeColorPalette, closeColorPalette, editor]);
38909
+ }, [applyInlineColor, closeColorPalette]);
38910
+ const applyCustomColor = (0, import_react75.useCallback)((color) => {
38911
+ applyInlineColor(color, false);
38912
+ }, [applyInlineColor]);
38853
38913
  (0, import_react75.useEffect)(() => {
38854
38914
  if (!showLinkInput) return;
38855
38915
  const close2 = () => setShowLinkInput(false);
@@ -39044,6 +39104,7 @@ var BubbleMenuContent = ({
39044
39104
  colors: isTextPalette ? textColors : highlightColors,
39045
39105
  currentColor: isTextPalette ? currentTextColor : isHighlightPalette ? currentHighlightColor : currentCellBgColor,
39046
39106
  onSelect: applyInlineColorAndClose,
39107
+ onCustomColorSelect: applyCustomColor,
39047
39108
  label: isTextPalette ? t("colors.textColor") : isHighlightPalette ? t("colors.highlight") : t("tableMenu.cellBackground") || "Cell background"
39048
39109
  }
39049
39110
  ) });
@@ -39330,6 +39391,7 @@ var BubbleMenuContent = ({
39330
39391
  /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
39331
39392
  ToolbarButton,
39332
39393
  {
39394
+ onMouseDown: () => onKeepOpenChange?.(true),
39333
39395
  onClick: () => setActiveColorPalette("cell-bg"),
39334
39396
  active: Boolean(currentCellBgColor),
39335
39397
  title: t("tableMenu.cellBackground") || "Cell background",
@@ -39561,6 +39623,8 @@ var CustomBubbleMenu = ({
39561
39623
  placement: "top"
39562
39624
  });
39563
39625
  const menuRef = (0, import_react75.useRef)(null);
39626
+ const updatePositionRef = (0, import_react75.useRef)(() => {
39627
+ });
39564
39628
  const keepOpenRef = (0, import_react75.useRef)(false);
39565
39629
  const [keepOpen, setKeepOpenState] = (0, import_react75.useState)(false);
39566
39630
  const showTimeoutRef = (0, import_react75.useRef)(null);
@@ -39618,19 +39682,15 @@ var CustomBubbleMenu = ({
39618
39682
  setIsVisible(false);
39619
39683
  return;
39620
39684
  }
39621
- const viewportPadding = 8;
39622
39685
  const menuHeight = menuRef.current?.getBoundingClientRect().height || BUBBLE_MENU_ESTIMATED_HEIGHT;
39623
- const editorTop = view.dom.getBoundingClientRect().top;
39624
- const selectionTop = Math.min(start.top, end.top);
39625
- const selectionBottom = Math.max(start.bottom, end.bottom);
39626
- const hasRoomAboveEditor = selectionTop - BUBBLE_MENU_OFFSET - menuHeight >= editorTop + viewportPadding;
39627
- const placement = hasRoomAboveEditor ? "top" : "bottom";
39628
- const left = Math.min(
39629
- window.innerWidth - viewportPadding,
39630
- Math.max(viewportPadding, (start.left + end.left) / 2)
39631
- );
39632
- const top = placement === "top" ? Math.max(viewportPadding, selectionTop - BUBBLE_MENU_OFFSET) : Math.min(window.innerHeight - viewportPadding, selectionBottom + BUBBLE_MENU_OFFSET);
39633
- setPosition2({ top, left, placement });
39686
+ const menuWidth = menuRef.current?.getBoundingClientRect().width || 0;
39687
+ setPosition2(getBubbleMenuPosition({
39688
+ start,
39689
+ end,
39690
+ menuSize: { width: menuWidth, height: menuHeight },
39691
+ viewport: { width: window.innerWidth, height: window.innerHeight },
39692
+ offset: BUBBLE_MENU_OFFSET
39693
+ }));
39634
39694
  if (keepOpenRef.current) {
39635
39695
  clearShowTimeout();
39636
39696
  setIsVisible(true);
@@ -39659,8 +39719,11 @@ var CustomBubbleMenu = ({
39659
39719
  editor.on("transaction", schedulePositionUpdate);
39660
39720
  editor.on("focus", schedulePositionUpdate);
39661
39721
  editor.on("blur", handleBlur);
39722
+ updatePositionRef.current = updatePosition;
39662
39723
  schedulePositionUpdate();
39663
39724
  return () => {
39725
+ updatePositionRef.current = () => {
39726
+ };
39664
39727
  if (animationFrameId !== null) cancelAnimationFrame(animationFrameId);
39665
39728
  clearShowTimeout();
39666
39729
  editor.off("transaction", schedulePositionUpdate);
@@ -39668,6 +39731,24 @@ var CustomBubbleMenu = ({
39668
39731
  editor.off("blur", handleBlur);
39669
39732
  };
39670
39733
  }, [editor]);
39734
+ (0, import_react75.useEffect)(() => {
39735
+ if (!isVisible || typeof ResizeObserver === "undefined") return;
39736
+ const menu = menuRef.current;
39737
+ if (!menu) return;
39738
+ let animationFrameId = null;
39739
+ const observer = new ResizeObserver(() => {
39740
+ if (animationFrameId !== null) cancelAnimationFrame(animationFrameId);
39741
+ animationFrameId = requestAnimationFrame(() => {
39742
+ animationFrameId = null;
39743
+ updatePositionRef.current();
39744
+ });
39745
+ });
39746
+ observer.observe(menu);
39747
+ return () => {
39748
+ if (animationFrameId !== null) cancelAnimationFrame(animationFrameId);
39749
+ observer.disconnect();
39750
+ };
39751
+ }, [isVisible]);
39671
39752
  (0, import_react75.useEffect)(() => {
39672
39753
  if (!isVisible) return;
39673
39754
  const handlePointerDown = (event) => {