@underverse-ui/underverse 1.0.141 → 1.0.143

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.js CHANGED
@@ -25261,6 +25261,7 @@ import Typography from "@tiptap/extension-typography";
25261
25261
  import Subscript from "@tiptap/extension-subscript";
25262
25262
  import Superscript from "@tiptap/extension-superscript";
25263
25263
  import HorizontalRule from "@tiptap/extension-horizontal-rule";
25264
+ import Gapcursor from "@tiptap/extension-gapcursor";
25264
25265
  import { common, createLowlight } from "lowlight";
25265
25266
 
25266
25267
  // src/components/UEditor/slash-command.tsx
@@ -26051,7 +26052,6 @@ import { mergeAttributes as mergeAttributes4 } from "@tiptap/core";
26051
26052
  import { NodeViewWrapper as NodeViewWrapper4, ReactNodeViewRenderer as ReactNodeViewRenderer4 } from "@tiptap/react";
26052
26053
  import { jsx as jsx81, jsxs as jsxs67 } from "react/jsx-runtime";
26053
26054
  var MIN_IMAGE_SIZE_PX = 40;
26054
- var AXIS_LOCK_THRESHOLD_PX = 4;
26055
26055
  var IMAGE_LAYOUTS = /* @__PURE__ */ new Set(["block", "left", "right"]);
26056
26056
  var IMAGE_WIDTH_PRESETS = /* @__PURE__ */ new Set(["sm", "md", "lg"]);
26057
26057
  function toNullableNumber(value) {
@@ -26092,6 +26092,41 @@ function getImageLayoutStyles(layout) {
26092
26092
  }
26093
26093
  return {};
26094
26094
  }
26095
+ function getAspectRatio({
26096
+ img,
26097
+ widthAttr,
26098
+ heightAttr,
26099
+ fallbackWidth,
26100
+ fallbackHeight
26101
+ }) {
26102
+ if (widthAttr && heightAttr) return widthAttr / heightAttr;
26103
+ if (img.naturalWidth > 0 && img.naturalHeight > 0) return img.naturalWidth / img.naturalHeight;
26104
+ if (fallbackWidth > 0 && fallbackHeight > 0) return fallbackWidth / fallbackHeight;
26105
+ return 1;
26106
+ }
26107
+ function sizeFromWidth(width, aspect, maxWidth) {
26108
+ let nextW = clamp8(width, MIN_IMAGE_SIZE_PX, maxWidth);
26109
+ let nextH = nextW / aspect;
26110
+ if (nextH < MIN_IMAGE_SIZE_PX) {
26111
+ nextH = MIN_IMAGE_SIZE_PX;
26112
+ nextW = clamp8(nextH * aspect, MIN_IMAGE_SIZE_PX, maxWidth);
26113
+ nextH = nextW / aspect;
26114
+ }
26115
+ return { width: nextW, height: nextH };
26116
+ }
26117
+ function sizeFromHeight(height, aspect, maxWidth) {
26118
+ let nextH = Math.max(MIN_IMAGE_SIZE_PX, height);
26119
+ let nextW = nextH * aspect;
26120
+ if (nextW > maxWidth) {
26121
+ nextW = maxWidth;
26122
+ nextH = nextW / aspect;
26123
+ }
26124
+ if (nextW < MIN_IMAGE_SIZE_PX) {
26125
+ nextW = MIN_IMAGE_SIZE_PX;
26126
+ nextH = nextW / aspect;
26127
+ }
26128
+ return { width: nextW, height: nextH };
26129
+ }
26095
26130
  function ResizableImageNodeView(props) {
26096
26131
  const { node, selected, updateAttributes, editor, getPos } = props;
26097
26132
  const wrapperRef = useRef29(null);
@@ -26102,7 +26137,6 @@ function ResizableImageNodeView(props) {
26102
26137
  const heightAttr = toNullableNumber(node.attrs["height"]);
26103
26138
  const textAlign = String(node.attrs["textAlign"] ?? "");
26104
26139
  const imageLayout = parseImageLayout(node.attrs["imageLayout"]);
26105
- const preserveAspectByDefault = imageLayout === "left" || imageLayout === "right";
26106
26140
  const dragStateRef = useRef29(null);
26107
26141
  useEffect34(() => {
26108
26142
  const img = imgRef.current;
@@ -26126,7 +26160,13 @@ function ResizableImageNodeView(props) {
26126
26160
  const maxW = editorEl ? editorEl.getBoundingClientRect().width : Number.POSITIVE_INFINITY;
26127
26161
  const startW = Math.max(MIN_IMAGE_SIZE_PX, rect.width);
26128
26162
  const startH = Math.max(MIN_IMAGE_SIZE_PX, rect.height);
26129
- const aspect = startH > 0 ? startW / startH : 1;
26163
+ const aspect = getAspectRatio({
26164
+ img,
26165
+ widthAttr,
26166
+ heightAttr,
26167
+ fallbackWidth: startW,
26168
+ fallbackHeight: startH
26169
+ });
26130
26170
  dragStateRef.current = {
26131
26171
  pointerId: event.pointerId,
26132
26172
  startX: event.clientX,
@@ -26135,7 +26175,6 @@ function ResizableImageNodeView(props) {
26135
26175
  startH,
26136
26176
  lastW: startW,
26137
26177
  lastH: startH,
26138
- axis: null,
26139
26178
  aspect,
26140
26179
  maxW: Math.max(MIN_IMAGE_SIZE_PX, maxW)
26141
26180
  };
@@ -26149,24 +26188,9 @@ function ResizableImageNodeView(props) {
26149
26188
  if (event.pointerId !== drag.pointerId) return;
26150
26189
  const dx = event.clientX - drag.startX;
26151
26190
  const dy = event.clientY - drag.startY;
26152
- let nextW = drag.startW;
26153
- let nextH = drag.startH;
26154
- const shouldPreserveAspect = preserveAspectByDefault ? !event.ctrlKey : event.ctrlKey;
26155
- if (shouldPreserveAspect) {
26156
- if (Math.abs(dx) >= Math.abs(dy)) {
26157
- nextW = clamp8(drag.startW + dx, MIN_IMAGE_SIZE_PX, drag.maxW);
26158
- nextH = clamp8(nextW / drag.aspect, MIN_IMAGE_SIZE_PX, Number.POSITIVE_INFINITY);
26159
- } else {
26160
- nextH = clamp8(drag.startH + dy, MIN_IMAGE_SIZE_PX, Number.POSITIVE_INFINITY);
26161
- nextW = clamp8(nextH * drag.aspect, MIN_IMAGE_SIZE_PX, drag.maxW);
26162
- }
26163
- } else {
26164
- if (!drag.axis && (Math.abs(dx) > AXIS_LOCK_THRESHOLD_PX || Math.abs(dy) > AXIS_LOCK_THRESHOLD_PX)) {
26165
- drag.axis = Math.abs(dx) >= Math.abs(dy) ? "x" : "y";
26166
- }
26167
- if (drag.axis === "x") nextW = clamp8(drag.startW + dx, MIN_IMAGE_SIZE_PX, drag.maxW);
26168
- if (drag.axis === "y") nextH = clamp8(drag.startH + dy, MIN_IMAGE_SIZE_PX, Number.POSITIVE_INFINITY);
26169
- }
26191
+ const nextSize = Math.abs(dx) >= Math.abs(dy) ? sizeFromWidth(drag.startW + dx, drag.aspect, drag.maxW) : sizeFromHeight(drag.startH + dy, drag.aspect, drag.maxW);
26192
+ const nextW = nextSize.width;
26193
+ const nextH = nextSize.height;
26170
26194
  drag.lastW = nextW;
26171
26195
  drag.lastH = nextH;
26172
26196
  img.style.width = `${Math.round(nextW)}px`;
@@ -27089,6 +27113,7 @@ function buildUEditorExtensions({
27089
27113
  }
27090
27114
  }),
27091
27115
  HorizontalRule,
27116
+ Gapcursor,
27092
27117
  Link.configure({
27093
27118
  openOnClick: false,
27094
27119
  protocols: ["http", "https", "mailto", "tel"],
@@ -27122,7 +27147,7 @@ function buildUEditorExtensions({
27122
27147
  handleWidth: 10,
27123
27148
  allowTableNodeSelection: true,
27124
27149
  HTMLAttributes: {
27125
- class: "border-collapse w-full my-4"
27150
+ class: "border-collapse my-4"
27126
27151
  }
27127
27152
  }),
27128
27153
  table_row_default,
@@ -27205,6 +27230,7 @@ import {
27205
27230
  Subscript as SubscriptIcon,
27206
27231
  Superscript as SuperscriptIcon,
27207
27232
  Table as TableIcon,
27233
+ TableCellsMerge,
27208
27234
  Trash2 as Trash22,
27209
27235
  Type as Type2,
27210
27236
  Underline as UnderlineIcon,
@@ -27475,6 +27501,43 @@ function isSelectedImage(editor) {
27475
27501
  const { selection } = editor.state;
27476
27502
  return selection instanceof NodeSelection && selection.node.type.name === "image";
27477
27503
  }
27504
+ function toPositiveNumber(value) {
27505
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
27506
+ if (typeof value === "string") {
27507
+ const parsed = Number.parseInt(value, 10);
27508
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
27509
+ }
27510
+ return null;
27511
+ }
27512
+ function getImageElementAtSelection(editor, pos) {
27513
+ const nodeDom = editor.view.nodeDOM(pos);
27514
+ if (!(nodeDom instanceof HTMLElement)) return null;
27515
+ if (nodeDom.tagName === "IMG") return nodeDom;
27516
+ return nodeDom.querySelector("img");
27517
+ }
27518
+ function getImageAspectRatio(editor, attrs, pos) {
27519
+ const widthAttr = toPositiveNumber(attrs.width);
27520
+ const heightAttr = toPositiveNumber(attrs.height);
27521
+ if (widthAttr && heightAttr) return widthAttr / heightAttr;
27522
+ const imageElement = typeof pos === "number" ? getImageElementAtSelection(editor, pos) : null;
27523
+ if (!imageElement) return null;
27524
+ if (imageElement.naturalWidth > 0 && imageElement.naturalHeight > 0) {
27525
+ return imageElement.naturalWidth / imageElement.naturalHeight;
27526
+ }
27527
+ const rect = imageElement.getBoundingClientRect();
27528
+ if (rect.width > 0 && rect.height > 0) return rect.width / rect.height;
27529
+ const width = toPositiveNumber(imageElement.getAttribute("width")) ?? toPositiveNumber(imageElement.style.width);
27530
+ const height = toPositiveNumber(imageElement.getAttribute("height")) ?? toPositiveNumber(imageElement.style.height);
27531
+ return width && height ? width / height : null;
27532
+ }
27533
+ function getImagePresetAttributes(editor, width, preset, attrs, pos) {
27534
+ const aspect = getImageAspectRatio(editor, attrs, pos);
27535
+ return {
27536
+ width,
27537
+ height: aspect ? Math.round(width / aspect) : toPositiveNumber(attrs.height),
27538
+ imageWidthPreset: preset
27539
+ };
27540
+ }
27478
27541
  function applyImageLayout(editor, layout) {
27479
27542
  const { state, view } = editor;
27480
27543
  const { selection, schema } = state;
@@ -27509,15 +27572,15 @@ function applyImageWidthPreset(editor, preset) {
27509
27572
  const mode = attrs.imageLayout === "left" || attrs.imageLayout === "right" ? "wrap" : "block";
27510
27573
  const width = IMAGE_WIDTHS_BY_LAYOUT[mode][preset];
27511
27574
  if (!isSelectedImage(editor)) {
27512
- editor.chain().focus().updateAttributes("image", { width, imageWidthPreset: preset }).run();
27575
+ editor.chain().focus().updateAttributes("image", getImagePresetAttributes(editor, width, preset, attrs)).run();
27513
27576
  return;
27514
27577
  }
27515
27578
  const { state, view } = editor;
27516
27579
  const selection = state.selection;
27580
+ const nextAttrs = getImagePresetAttributes(editor, width, preset, selection.node.attrs, selection.from);
27517
27581
  const transaction = state.tr.setNodeMarkup(selection.from, void 0, {
27518
27582
  ...selection.node.attrs,
27519
- width,
27520
- imageWidthPreset: preset
27583
+ ...nextAttrs
27521
27584
  });
27522
27585
  view.dispatch(transaction.scrollIntoView());
27523
27586
  view.focus();
@@ -27645,6 +27708,229 @@ var ImageInput = ({ onSubmit, onCancel }) => {
27645
27708
  ] });
27646
27709
  };
27647
27710
 
27711
+ // src/components/UEditor/table-cell-commands.ts
27712
+ import { TextSelection as TextSelection2 } from "@tiptap/pm/state";
27713
+ import { selectedRect, TableMap } from "@tiptap/pm/tables";
27714
+ function getCellSelectionPositions(selection) {
27715
+ const value = selection;
27716
+ const anchor = value.$anchorCell?.pos;
27717
+ const head = value.$headCell?.pos;
27718
+ return typeof anchor === "number" && typeof head === "number" ? { anchor, head } : null;
27719
+ }
27720
+ function findTableInfoFromCellPos(editor, cellPos) {
27721
+ const $pos = editor.state.doc.resolve(cellPos);
27722
+ for (let depth = $pos.depth; depth > 0; depth -= 1) {
27723
+ const node = $pos.node(depth);
27724
+ if (node.type.name === "table") {
27725
+ return {
27726
+ table: node,
27727
+ tablePos: $pos.before(depth),
27728
+ tableStart: $pos.start(depth)
27729
+ };
27730
+ }
27731
+ }
27732
+ return null;
27733
+ }
27734
+ function getFocusableCellPos(editor, cellPos) {
27735
+ const cellNode = editor.state.doc.nodeAt(cellPos);
27736
+ if (!cellNode) return cellPos + 1;
27737
+ let offset = cellPos + 1;
27738
+ let node = cellNode.firstChild ?? null;
27739
+ while (node && !node.isTextblock) {
27740
+ offset += 1;
27741
+ node = node.firstChild ?? null;
27742
+ }
27743
+ return node?.isTextblock ? offset + 1 : cellPos + 1;
27744
+ }
27745
+ function focusCell(editor, cellPos) {
27746
+ const selection = TextSelection2.near(editor.state.doc.resolve(getFocusableCellPos(editor, cellPos)));
27747
+ editor.view.dispatch(editor.state.tr.setSelection(selection));
27748
+ editor.view.focus();
27749
+ }
27750
+ function collectChildren(node) {
27751
+ const children = [];
27752
+ node.forEach((child) => children.push(child));
27753
+ return children;
27754
+ }
27755
+ function createEmptyCellNode(cellNode) {
27756
+ return cellNode.type.createAndFill(cellNode.attrs) ?? cellNode;
27757
+ }
27758
+ function getSelectedTableRect(editor) {
27759
+ const cellSelection = getCellSelectionPositions(editor.state.selection);
27760
+ if (cellSelection) {
27761
+ const tableInfo = findTableInfoFromCellPos(editor, cellSelection.anchor);
27762
+ if (tableInfo) {
27763
+ const map = TableMap.get(tableInfo.table);
27764
+ const rect = map.rectBetween(
27765
+ cellSelection.anchor - tableInfo.tableStart,
27766
+ cellSelection.head - tableInfo.tableStart
27767
+ );
27768
+ return {
27769
+ ...rect,
27770
+ map,
27771
+ table: tableInfo.table,
27772
+ tableStart: tableInfo.tableStart
27773
+ };
27774
+ }
27775
+ }
27776
+ return selectedRect(editor.state);
27777
+ }
27778
+ function parsePixelWidth(value) {
27779
+ if (!value) return null;
27780
+ const parsed = Number.parseFloat(value);
27781
+ return Number.isFinite(parsed) && parsed > 0 ? Math.round(parsed) : null;
27782
+ }
27783
+ function getDomColumnWidths(editor, rect) {
27784
+ const tableDom = editor.view.nodeDOM(rect.tableStart - 1);
27785
+ if (!(tableDom instanceof HTMLTableElement)) return null;
27786
+ const cols = Array.from(tableDom.querySelectorAll("colgroup > col"));
27787
+ if (cols.length === 0) return null;
27788
+ const widths = [];
27789
+ for (let col = rect.left; col < rect.right; col += 1) {
27790
+ const colElement = cols[col];
27791
+ if (!colElement) return null;
27792
+ const width = parsePixelWidth(colElement.style.width) ?? parsePixelWidth(colElement.getAttribute("width")) ?? Math.round(colElement.getBoundingClientRect().width);
27793
+ if (!Number.isFinite(width) || width <= 0) return null;
27794
+ widths.push(width);
27795
+ }
27796
+ return widths.length > 0 ? widths : null;
27797
+ }
27798
+ function getNodeColumnWidths(rect) {
27799
+ const widths = [];
27800
+ for (let col = rect.left; col < rect.right; col += 1) {
27801
+ let width = null;
27802
+ const seen = /* @__PURE__ */ new Set();
27803
+ for (let row = 0; row < rect.map.height && width == null; row += 1) {
27804
+ const cellPos = rect.map.map[row * rect.map.width + col];
27805
+ if (seen.has(cellPos)) continue;
27806
+ seen.add(cellPos);
27807
+ const cell = rect.table.nodeAt(cellPos);
27808
+ const colwidth = cell?.attrs.colwidth;
27809
+ if (!Array.isArray(colwidth)) continue;
27810
+ const cellLeft = rect.map.colCount(cellPos);
27811
+ const widthIndex = col - cellLeft;
27812
+ const candidate = colwidth[widthIndex];
27813
+ if (typeof candidate === "number" && candidate > 0) {
27814
+ width = candidate;
27815
+ }
27816
+ }
27817
+ if (width == null) return null;
27818
+ widths.push(width);
27819
+ }
27820
+ return widths.length > 0 ? widths : null;
27821
+ }
27822
+ function getSelectedColumnWidths(editor, rect) {
27823
+ return getDomColumnWidths(editor, rect) ?? getNodeColumnWidths(rect);
27824
+ }
27825
+ function dispatchTableLayoutChange(editor) {
27826
+ editor.view.dom.dispatchEvent(new CustomEvent(UEDITOR_TABLE_LAYOUT_CHANGE_EVENT, { bubbles: true }));
27827
+ }
27828
+ function mergeTableCellsPreservingColumnWidths(editor) {
27829
+ const rect = getSelectedTableRect(editor);
27830
+ const widths = getSelectedColumnWidths(editor, rect);
27831
+ const merged = editor.chain().focus().mergeCells().run();
27832
+ if (!merged) return merged;
27833
+ if (!widths) {
27834
+ dispatchTableLayoutChange(editor);
27835
+ return merged;
27836
+ }
27837
+ const nextRect = getSelectedTableRect(editor);
27838
+ const cellPos = nextRect.map.map[nextRect.top * nextRect.map.width + nextRect.left];
27839
+ const absolutePos = nextRect.tableStart + cellPos;
27840
+ const node = editor.state.doc.nodeAt(absolutePos);
27841
+ if (!node) return merged;
27842
+ editor.view.dispatch(
27843
+ editor.state.tr.setNodeMarkup(absolutePos, node.type, {
27844
+ ...node.attrs,
27845
+ colwidth: widths
27846
+ })
27847
+ );
27848
+ dispatchTableLayoutChange(editor);
27849
+ return true;
27850
+ }
27851
+ function runTableCommandAtCellPos(editor, cellPos, command) {
27852
+ if (cellPos == null) return false;
27853
+ focusCell(editor, cellPos);
27854
+ return command(editor.chain().focus(null, { scrollIntoView: false })).run();
27855
+ }
27856
+ function getTableCornerCellPos(editor, activePos) {
27857
+ const tableInfo = findTableInfoFromCellPos(editor, activePos);
27858
+ if (!tableInfo) return null;
27859
+ const map = TableMap.get(tableInfo.table);
27860
+ return tableInfo.tableStart + map.positionAt(map.height - 1, map.width - 1, tableInfo.table);
27861
+ }
27862
+ function replaceTableAtCellPos(editor, cellPos, updateTable) {
27863
+ if (cellPos == null) return false;
27864
+ const tableInfo = findTableInfoFromCellPos(editor, cellPos);
27865
+ if (!tableInfo) return false;
27866
+ const nextTable = updateTable(tableInfo.table);
27867
+ if (!nextTable) return false;
27868
+ editor.view.dispatch(editor.state.tr.replaceWith(tableInfo.tablePos, tableInfo.tablePos + tableInfo.table.nodeSize, nextTable));
27869
+ dispatchTableLayoutChange(editor);
27870
+ return true;
27871
+ }
27872
+ function duplicateTableRowAt(editor, rowIndex, cellPos) {
27873
+ return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
27874
+ const rows = collectChildren(tableNode);
27875
+ const rowNode = rows[rowIndex];
27876
+ if (!rowNode) return null;
27877
+ rows.splice(rowIndex + 1, 0, rowNode.copy(rowNode.content));
27878
+ return tableNode.type.create(tableNode.attrs, rows);
27879
+ });
27880
+ }
27881
+ function clearTableRowAt(editor, rowIndex, cellPos) {
27882
+ return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
27883
+ const rows = collectChildren(tableNode);
27884
+ const rowNode = rows[rowIndex];
27885
+ if (!rowNode) return null;
27886
+ const cells = collectChildren(rowNode).map((cellNode) => createEmptyCellNode(cellNode));
27887
+ rows[rowIndex] = rowNode.type.create(rowNode.attrs, cells);
27888
+ return tableNode.type.create(tableNode.attrs, rows);
27889
+ });
27890
+ }
27891
+ function duplicateTableColumnAt(editor, columnIndex, cellPos) {
27892
+ return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
27893
+ const rows = collectChildren(tableNode).map((rowNode) => {
27894
+ const cells = collectChildren(rowNode);
27895
+ const cellNode = cells[columnIndex];
27896
+ if (!cellNode) return rowNode;
27897
+ cells.splice(columnIndex + 1, 0, cellNode.copy(cellNode.content));
27898
+ return rowNode.type.create(rowNode.attrs, cells);
27899
+ });
27900
+ return tableNode.type.create(tableNode.attrs, rows);
27901
+ });
27902
+ }
27903
+ function clearTableColumnAt(editor, columnIndex, cellPos) {
27904
+ return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
27905
+ const rows = collectChildren(tableNode).map((rowNode) => {
27906
+ const cells = collectChildren(rowNode);
27907
+ const cellNode = cells[columnIndex];
27908
+ if (!cellNode) return rowNode;
27909
+ cells[columnIndex] = createEmptyCellNode(cellNode);
27910
+ return rowNode.type.create(rowNode.attrs, cells);
27911
+ });
27912
+ return tableNode.type.create(tableNode.attrs, rows);
27913
+ });
27914
+ }
27915
+ function expandTableFromCell(editor, activeCellPos, rows, columns) {
27916
+ let cornerCellPos = getTableCornerCellPos(editor, activeCellPos);
27917
+ if (cornerCellPos == null) return false;
27918
+ for (let index = 0; index < rows; index += 1) {
27919
+ const ok = runTableCommandAtCellPos(editor, cornerCellPos, (chain) => chain.addRowAfter());
27920
+ if (!ok) return false;
27921
+ cornerCellPos = getTableCornerCellPos(editor, cornerCellPos);
27922
+ if (cornerCellPos == null) return false;
27923
+ }
27924
+ for (let index = 0; index < columns; index += 1) {
27925
+ const ok = runTableCommandAtCellPos(editor, cornerCellPos, (chain) => chain.addColumnAfter());
27926
+ if (!ok) return false;
27927
+ cornerCellPos = getTableCornerCellPos(editor, cornerCellPos);
27928
+ if (cornerCellPos == null) return false;
27929
+ }
27930
+ dispatchTableLayoutChange(editor);
27931
+ return true;
27932
+ }
27933
+
27648
27934
  // src/components/UEditor/typography-options.ts
27649
27935
  function normalizeStyleValue(value) {
27650
27936
  return typeof value === "string" ? value.trim().replace(/^['"]|['"]$/g, "") : "";
@@ -27840,6 +28126,8 @@ var EditorToolbar = ({
27840
28126
  const currentTableAlign = tableAlignAttr === "center" || tableAlignAttr === "right" ? tableAlignAttr : "left";
27841
28127
  const isTableSelected = tableInfo !== null;
27842
28128
  const hasTableContext = isTableSelected || tableCommandAnchorPosRef.current !== null;
28129
+ const canMergeCells = hasTableContext && editor.can().mergeCells();
28130
+ const canSplitCell = hasTableContext && editor.can().splitCell();
27843
28131
  const currentFontFamily = normalizeStyleValue(textStyleAttrs.fontFamily);
27844
28132
  const currentFontSize = normalizeStyleValue(textStyleAttrs.fontSize);
27845
28133
  const currentTextColor = normalizeStyleValue(textStyleAttrs.color) || "inherit";
@@ -28609,13 +28897,68 @@ var EditorToolbar = ({
28609
28897
  ),
28610
28898
  /* @__PURE__ */ jsx85(ToolbarDivider, {}),
28611
28899
  /* @__PURE__ */ jsx85(ToolbarButton, { onClick: () => editor.chain().focus().undo().run(), disabled: !editor.can().undo(), title: t("toolbar.undo"), children: /* @__PURE__ */ jsx85(UndoIcon, { className: "w-4 h-4" }) }),
28612
- /* @__PURE__ */ jsx85(ToolbarButton, { onClick: () => editor.chain().focus().redo().run(), disabled: !editor.can().redo(), title: t("toolbar.redo"), children: /* @__PURE__ */ jsx85(RedoIcon, { className: "w-4 h-4" }) })
28613
- ] });
28614
- };
28615
-
28616
- // src/components/UEditor/menus.tsx
28617
- import { useCallback as useCallback22, useEffect as useEffect36, useMemo as useMemo23, useRef as useRef33, useState as useState48 } from "react";
28618
- import { useEditorState as useEditorState2 } from "@tiptap/react";
28900
+ /* @__PURE__ */ jsx85(ToolbarButton, { onClick: () => editor.chain().focus().redo().run(), disabled: !editor.can().redo(), title: t("toolbar.redo"), children: /* @__PURE__ */ jsx85(RedoIcon, { className: "w-4 h-4" }) }),
28901
+ hasTableContext && /* @__PURE__ */ jsxs71(Fragment28, { children: [
28902
+ /* @__PURE__ */ jsx85(ToolbarDivider, {}),
28903
+ /* @__PURE__ */ jsx85(
28904
+ ToolbarButton,
28905
+ {
28906
+ onClick: () => editor.chain().focus().addColumnBefore().run(),
28907
+ disabled: !editor.can().addColumnBefore(),
28908
+ title: t("tableMenu.addColumnBefore"),
28909
+ children: /* @__PURE__ */ jsx85(ArrowLeft, { className: "w-4 h-4" })
28910
+ }
28911
+ ),
28912
+ /* @__PURE__ */ jsx85(
28913
+ ToolbarButton,
28914
+ {
28915
+ onClick: () => editor.chain().focus().addColumnAfter().run(),
28916
+ disabled: !editor.can().addColumnAfter(),
28917
+ title: t("tableMenu.addColumnAfter"),
28918
+ children: /* @__PURE__ */ jsx85(ArrowRight, { className: "w-4 h-4" })
28919
+ }
28920
+ ),
28921
+ /* @__PURE__ */ jsx85(
28922
+ ToolbarButton,
28923
+ {
28924
+ onClick: () => editor.chain().focus().addRowBefore().run(),
28925
+ disabled: !editor.can().addRowBefore(),
28926
+ title: t("tableMenu.addRowBefore"),
28927
+ children: /* @__PURE__ */ jsx85(ArrowUp, { className: "w-4 h-4" })
28928
+ }
28929
+ ),
28930
+ /* @__PURE__ */ jsx85(
28931
+ ToolbarButton,
28932
+ {
28933
+ onClick: () => editor.chain().focus().addRowAfter().run(),
28934
+ disabled: !editor.can().addRowAfter(),
28935
+ title: t("tableMenu.addRowAfter"),
28936
+ children: /* @__PURE__ */ jsx85(ArrowDown, { className: "w-4 h-4" })
28937
+ }
28938
+ ),
28939
+ /* @__PURE__ */ jsx85(
28940
+ ToolbarButton,
28941
+ {
28942
+ onClick: () => {
28943
+ if (canSplitCell) {
28944
+ editor.chain().focus().splitCell().run();
28945
+ return;
28946
+ }
28947
+ mergeTableCellsPreservingColumnWidths(editor);
28948
+ },
28949
+ active: canSplitCell,
28950
+ disabled: !canMergeCells && !canSplitCell,
28951
+ title: canSplitCell ? t("tableMenu.splitCell") : t("tableMenu.mergeCells"),
28952
+ children: /* @__PURE__ */ jsx85(TableCellsMerge, { className: "w-4 h-4" })
28953
+ }
28954
+ )
28955
+ ] })
28956
+ ] });
28957
+ };
28958
+
28959
+ // src/components/UEditor/menus.tsx
28960
+ import { useCallback as useCallback22, useEffect as useEffect36, useMemo as useMemo23, useRef as useRef33, useState as useState48 } from "react";
28961
+ import { useEditorState as useEditorState2 } from "@tiptap/react";
28619
28962
  import { isInTable as isSelectionInTable, setCellAttr } from "@tiptap/pm/tables";
28620
28963
  import { createPortal as createPortal8 } from "react-dom";
28621
28964
  import {
@@ -28631,7 +28974,7 @@ import {
28631
28974
  RotateCcw as RotateCcw3,
28632
28975
  Subscript as SubscriptIcon2,
28633
28976
  Superscript as SuperscriptIcon2,
28634
- TableCellsMerge,
28977
+ TableCellsMerge as TableCellsMerge2,
28635
28978
  Trash2 as Trash23,
28636
28979
  Type as Type3,
28637
28980
  Underline as UnderlineIcon2,
@@ -28640,231 +28983,6 @@ import {
28640
28983
  Unlink,
28641
28984
  ExternalLink as ExternalLink3
28642
28985
  } from "lucide-react";
28643
-
28644
- // src/components/UEditor/table-cell-commands.ts
28645
- import { TextSelection as TextSelection2 } from "@tiptap/pm/state";
28646
- import { selectedRect, TableMap } from "@tiptap/pm/tables";
28647
- function getCellSelectionPositions(selection) {
28648
- const value = selection;
28649
- const anchor = value.$anchorCell?.pos;
28650
- const head = value.$headCell?.pos;
28651
- return typeof anchor === "number" && typeof head === "number" ? { anchor, head } : null;
28652
- }
28653
- function findTableInfoFromCellPos(editor, cellPos) {
28654
- const $pos = editor.state.doc.resolve(cellPos);
28655
- for (let depth = $pos.depth; depth > 0; depth -= 1) {
28656
- const node = $pos.node(depth);
28657
- if (node.type.name === "table") {
28658
- return {
28659
- table: node,
28660
- tablePos: $pos.before(depth),
28661
- tableStart: $pos.start(depth)
28662
- };
28663
- }
28664
- }
28665
- return null;
28666
- }
28667
- function getFocusableCellPos(editor, cellPos) {
28668
- const cellNode = editor.state.doc.nodeAt(cellPos);
28669
- if (!cellNode) return cellPos + 1;
28670
- let offset = cellPos + 1;
28671
- let node = cellNode.firstChild ?? null;
28672
- while (node && !node.isTextblock) {
28673
- offset += 1;
28674
- node = node.firstChild ?? null;
28675
- }
28676
- return node?.isTextblock ? offset + 1 : cellPos + 1;
28677
- }
28678
- function focusCell(editor, cellPos) {
28679
- const selection = TextSelection2.near(editor.state.doc.resolve(getFocusableCellPos(editor, cellPos)));
28680
- editor.view.dispatch(editor.state.tr.setSelection(selection));
28681
- editor.view.focus();
28682
- }
28683
- function collectChildren(node) {
28684
- const children = [];
28685
- node.forEach((child) => children.push(child));
28686
- return children;
28687
- }
28688
- function createEmptyCellNode(cellNode) {
28689
- return cellNode.type.createAndFill(cellNode.attrs) ?? cellNode;
28690
- }
28691
- function getSelectedTableRect(editor) {
28692
- const cellSelection = getCellSelectionPositions(editor.state.selection);
28693
- if (cellSelection) {
28694
- const tableInfo = findTableInfoFromCellPos(editor, cellSelection.anchor);
28695
- if (tableInfo) {
28696
- const map = TableMap.get(tableInfo.table);
28697
- const rect = map.rectBetween(
28698
- cellSelection.anchor - tableInfo.tableStart,
28699
- cellSelection.head - tableInfo.tableStart
28700
- );
28701
- return {
28702
- ...rect,
28703
- map,
28704
- table: tableInfo.table,
28705
- tableStart: tableInfo.tableStart
28706
- };
28707
- }
28708
- }
28709
- return selectedRect(editor.state);
28710
- }
28711
- function parsePixelWidth(value) {
28712
- if (!value) return null;
28713
- const parsed = Number.parseFloat(value);
28714
- return Number.isFinite(parsed) && parsed > 0 ? Math.round(parsed) : null;
28715
- }
28716
- function getDomColumnWidths(editor, rect) {
28717
- const tableDom = editor.view.nodeDOM(rect.tableStart - 1);
28718
- if (!(tableDom instanceof HTMLTableElement)) return null;
28719
- const cols = Array.from(tableDom.querySelectorAll("colgroup > col"));
28720
- if (cols.length === 0) return null;
28721
- const widths = [];
28722
- for (let col = rect.left; col < rect.right; col += 1) {
28723
- const colElement = cols[col];
28724
- if (!colElement) return null;
28725
- const width = parsePixelWidth(colElement.style.width) ?? parsePixelWidth(colElement.getAttribute("width")) ?? Math.round(colElement.getBoundingClientRect().width);
28726
- if (!Number.isFinite(width) || width <= 0) return null;
28727
- widths.push(width);
28728
- }
28729
- return widths.length > 0 ? widths : null;
28730
- }
28731
- function getNodeColumnWidths(rect) {
28732
- const widths = [];
28733
- for (let col = rect.left; col < rect.right; col += 1) {
28734
- let width = null;
28735
- const seen = /* @__PURE__ */ new Set();
28736
- for (let row = 0; row < rect.map.height && width == null; row += 1) {
28737
- const cellPos = rect.map.map[row * rect.map.width + col];
28738
- if (seen.has(cellPos)) continue;
28739
- seen.add(cellPos);
28740
- const cell = rect.table.nodeAt(cellPos);
28741
- const colwidth = cell?.attrs.colwidth;
28742
- if (!Array.isArray(colwidth)) continue;
28743
- const cellLeft = rect.map.colCount(cellPos);
28744
- const widthIndex = col - cellLeft;
28745
- const candidate = colwidth[widthIndex];
28746
- if (typeof candidate === "number" && candidate > 0) {
28747
- width = candidate;
28748
- }
28749
- }
28750
- if (width == null) return null;
28751
- widths.push(width);
28752
- }
28753
- return widths.length > 0 ? widths : null;
28754
- }
28755
- function getSelectedColumnWidths(editor, rect) {
28756
- return getDomColumnWidths(editor, rect) ?? getNodeColumnWidths(rect);
28757
- }
28758
- function dispatchTableLayoutChange(editor) {
28759
- editor.view.dom.dispatchEvent(new CustomEvent(UEDITOR_TABLE_LAYOUT_CHANGE_EVENT, { bubbles: true }));
28760
- }
28761
- function mergeTableCellsPreservingColumnWidths(editor) {
28762
- const rect = getSelectedTableRect(editor);
28763
- const widths = getSelectedColumnWidths(editor, rect);
28764
- const merged = editor.chain().focus().mergeCells().run();
28765
- if (!merged) return merged;
28766
- if (!widths) {
28767
- dispatchTableLayoutChange(editor);
28768
- return merged;
28769
- }
28770
- const nextRect = getSelectedTableRect(editor);
28771
- const cellPos = nextRect.map.map[nextRect.top * nextRect.map.width + nextRect.left];
28772
- const absolutePos = nextRect.tableStart + cellPos;
28773
- const node = editor.state.doc.nodeAt(absolutePos);
28774
- if (!node) return merged;
28775
- editor.view.dispatch(
28776
- editor.state.tr.setNodeMarkup(absolutePos, node.type, {
28777
- ...node.attrs,
28778
- colwidth: widths
28779
- })
28780
- );
28781
- dispatchTableLayoutChange(editor);
28782
- return true;
28783
- }
28784
- function runTableCommandAtCellPos(editor, cellPos, command) {
28785
- if (cellPos == null) return false;
28786
- focusCell(editor, cellPos);
28787
- return command(editor.chain().focus(null, { scrollIntoView: false })).run();
28788
- }
28789
- function getTableCornerCellPos(editor, activePos) {
28790
- const tableInfo = findTableInfoFromCellPos(editor, activePos);
28791
- if (!tableInfo) return null;
28792
- const map = TableMap.get(tableInfo.table);
28793
- return tableInfo.tableStart + map.positionAt(map.height - 1, map.width - 1, tableInfo.table);
28794
- }
28795
- function replaceTableAtCellPos(editor, cellPos, updateTable) {
28796
- if (cellPos == null) return false;
28797
- const tableInfo = findTableInfoFromCellPos(editor, cellPos);
28798
- if (!tableInfo) return false;
28799
- const nextTable = updateTable(tableInfo.table);
28800
- if (!nextTable) return false;
28801
- editor.view.dispatch(editor.state.tr.replaceWith(tableInfo.tablePos, tableInfo.tablePos + tableInfo.table.nodeSize, nextTable));
28802
- dispatchTableLayoutChange(editor);
28803
- return true;
28804
- }
28805
- function duplicateTableRowAt(editor, rowIndex, cellPos) {
28806
- return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
28807
- const rows = collectChildren(tableNode);
28808
- const rowNode = rows[rowIndex];
28809
- if (!rowNode) return null;
28810
- rows.splice(rowIndex + 1, 0, rowNode.copy(rowNode.content));
28811
- return tableNode.type.create(tableNode.attrs, rows);
28812
- });
28813
- }
28814
- function clearTableRowAt(editor, rowIndex, cellPos) {
28815
- return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
28816
- const rows = collectChildren(tableNode);
28817
- const rowNode = rows[rowIndex];
28818
- if (!rowNode) return null;
28819
- const cells = collectChildren(rowNode).map((cellNode) => createEmptyCellNode(cellNode));
28820
- rows[rowIndex] = rowNode.type.create(rowNode.attrs, cells);
28821
- return tableNode.type.create(tableNode.attrs, rows);
28822
- });
28823
- }
28824
- function duplicateTableColumnAt(editor, columnIndex, cellPos) {
28825
- return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
28826
- const rows = collectChildren(tableNode).map((rowNode) => {
28827
- const cells = collectChildren(rowNode);
28828
- const cellNode = cells[columnIndex];
28829
- if (!cellNode) return rowNode;
28830
- cells.splice(columnIndex + 1, 0, cellNode.copy(cellNode.content));
28831
- return rowNode.type.create(rowNode.attrs, cells);
28832
- });
28833
- return tableNode.type.create(tableNode.attrs, rows);
28834
- });
28835
- }
28836
- function clearTableColumnAt(editor, columnIndex, cellPos) {
28837
- return replaceTableAtCellPos(editor, cellPos, (tableNode) => {
28838
- const rows = collectChildren(tableNode).map((rowNode) => {
28839
- const cells = collectChildren(rowNode);
28840
- const cellNode = cells[columnIndex];
28841
- if (!cellNode) return rowNode;
28842
- cells[columnIndex] = createEmptyCellNode(cellNode);
28843
- return rowNode.type.create(rowNode.attrs, cells);
28844
- });
28845
- return tableNode.type.create(tableNode.attrs, rows);
28846
- });
28847
- }
28848
- function expandTableFromCell(editor, activeCellPos, rows, columns) {
28849
- let cornerCellPos = getTableCornerCellPos(editor, activeCellPos);
28850
- if (cornerCellPos == null) return false;
28851
- for (let index = 0; index < rows; index += 1) {
28852
- const ok = runTableCommandAtCellPos(editor, cornerCellPos, (chain) => chain.addRowAfter());
28853
- if (!ok) return false;
28854
- cornerCellPos = getTableCornerCellPos(editor, cornerCellPos);
28855
- if (cornerCellPos == null) return false;
28856
- }
28857
- for (let index = 0; index < columns; index += 1) {
28858
- const ok = runTableCommandAtCellPos(editor, cornerCellPos, (chain) => chain.addColumnAfter());
28859
- if (!ok) return false;
28860
- cornerCellPos = getTableCornerCellPos(editor, cornerCellPos);
28861
- if (cornerCellPos == null) return false;
28862
- }
28863
- dispatchTableLayoutChange(editor);
28864
- return true;
28865
- }
28866
-
28867
- // src/components/UEditor/menus.tsx
28868
28986
  import { Fragment as Fragment29, jsx as jsx86, jsxs as jsxs72 } from "react/jsx-runtime";
28869
28987
  var FloatingSlashCommandMenu = ({ editor, onClose }) => {
28870
28988
  const t = useSmartTranslations("UEditor");
@@ -29401,7 +29519,7 @@ var BubbleMenuContent = ({
29401
29519
  active: canSplitCell,
29402
29520
  disabled: !canMergeCells && !canSplitCell,
29403
29521
  title: canSplitCell ? t("tableMenu.splitCell") || "Split cell" : t("tableMenu.mergeCells") || "Merge cells",
29404
- children: /* @__PURE__ */ jsx86(TableCellsMerge, { className: "w-4 h-4" })
29522
+ children: /* @__PURE__ */ jsx86(TableCellsMerge2, { className: "w-4 h-4" })
29405
29523
  }
29406
29524
  )
29407
29525
  ] }),
@@ -29465,10 +29583,22 @@ var CustomBubbleMenu = ({
29465
29583
  setIsVisible(false);
29466
29584
  return;
29467
29585
  }
29468
- const start = view.coordsAtPos(from);
29469
- const end = view.coordsAtPos(to);
29470
- const left = (start.left + end.left) / 2;
29471
- const top = start.top - BUBBLE_MENU_OFFSET;
29586
+ let start;
29587
+ let end;
29588
+ try {
29589
+ start = view.coordsAtPos(from);
29590
+ end = view.coordsAtPos(to);
29591
+ } catch {
29592
+ clearShowTimeout();
29593
+ setIsVisible(false);
29594
+ return;
29595
+ }
29596
+ const viewportPadding = 8;
29597
+ const left = Math.min(
29598
+ window.innerWidth - viewportPadding,
29599
+ Math.max(viewportPadding, (start.left + end.left) / 2)
29600
+ );
29601
+ const top = Math.max(viewportPadding, start.top - BUBBLE_MENU_OFFSET);
29472
29602
  setPosition({ top, left });
29473
29603
  if (keepOpenRef.current) {
29474
29604
  clearShowTimeout();
@@ -29490,11 +29620,17 @@ var CustomBubbleMenu = ({
29490
29620
  editor.on("selectionUpdate", updatePosition);
29491
29621
  editor.on("focus", updatePosition);
29492
29622
  editor.on("blur", handleBlur);
29623
+ editor.on("transaction", updatePosition);
29624
+ editor.on("update", updatePosition);
29625
+ const animationFrameId = requestAnimationFrame(updatePosition);
29493
29626
  return () => {
29627
+ cancelAnimationFrame(animationFrameId);
29494
29628
  clearShowTimeout();
29495
29629
  editor.off("selectionUpdate", updatePosition);
29496
29630
  editor.off("focus", updatePosition);
29497
29631
  editor.off("blur", handleBlur);
29632
+ editor.off("transaction", updatePosition);
29633
+ editor.off("update", updatePosition);
29498
29634
  };
29499
29635
  }, [editor]);
29500
29636
  if (!isVisible) return null;
@@ -29504,7 +29640,7 @@ var CustomBubbleMenu = ({
29504
29640
  {
29505
29641
  ref: menuRef,
29506
29642
  "data-popover": true,
29507
- className: "fixed z-99999 flex rounded-xl border border-border/40 bg-card text-card-foreground shadow-lg backdrop-blur-sm overflow-hidden animate-in fade-in-0 zoom-in-95",
29643
+ className: "fixed z-[99999] flex rounded-xl border border-border/40 bg-card text-card-foreground shadow-lg backdrop-blur-sm overflow-hidden animate-in fade-in-0 zoom-in-95",
29508
29644
  style: {
29509
29645
  top: `${position.top}px`,
29510
29646
  left: `${position.left}px`,
@@ -35427,6 +35563,17 @@ var UEDITOR_PROSEMIRROR_CLASS_NAME = cn(
35427
35563
  "[&_.is-editor-empty]:before:float-left",
35428
35564
  "[&_.is-editor-empty]:before:pointer-events-none",
35429
35565
  "[&_.is-editor-empty]:before:h-0",
35566
+ "[&_.ProseMirror-gapcursor]:pointer-events-none",
35567
+ "[&_.ProseMirror-gapcursor]:absolute",
35568
+ "[&_.ProseMirror-gapcursor]:hidden",
35569
+ "[&_.ProseMirror-gapcursor:after]:content-['']",
35570
+ "[&_.ProseMirror-gapcursor:after]:block",
35571
+ "[&_.ProseMirror-gapcursor:after]:absolute",
35572
+ "[&_.ProseMirror-gapcursor:after]:top-[-2px]",
35573
+ "[&_.ProseMirror-gapcursor:after]:w-8",
35574
+ "[&_.ProseMirror-gapcursor:after]:border-t-2",
35575
+ "[&_.ProseMirror-gapcursor:after]:border-primary",
35576
+ "[&.ProseMirror-focused_.ProseMirror-gapcursor]:block",
35430
35577
  "[&_ul[data-type='taskList']]:list-none",
35431
35578
  "[&_ul[data-type='taskList']]:pl-0",
35432
35579
  "[&_ul[data-type='taskList']_li]:flex",
@@ -35459,6 +35606,7 @@ var UEDITOR_PROSEMIRROR_CLASS_NAME = cn(
35459
35606
  "[&_.tableWrapper::-webkit-scrollbar-thumb]:border-transparent",
35460
35607
  "[&_.tableWrapper::-webkit-scrollbar-thumb]:bg-border/70",
35461
35608
  "[&_.tableWrapper::-webkit-scrollbar-thumb:hover]:bg-muted-foreground/45",
35609
+ "[&_table]:w-auto",
35462
35610
  "[&_table]:table-fixed",
35463
35611
  "[&_table]:overflow-hidden",
35464
35612
  "[&_table]:select-text",
@@ -36741,7 +36889,7 @@ var MenuBar = ({
36741
36889
  "div",
36742
36890
  {
36743
36891
  "data-testid": "preview-content",
36744
- className: "prose prose-sm sm:prose dark:prose-invert max-w-none p-2",
36892
+ className: UEDITOR_PROSEMIRROR_CLASS_NAME,
36745
36893
  dangerouslySetInnerHTML: { __html: editor.getHTML() }
36746
36894
  }
36747
36895
  )