Add split cells action

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-03-25 16:09:42 +04:00
parent 935e650991
commit 48fbca6b04
2 changed files with 34 additions and 3 deletions

View File

@@ -17,7 +17,7 @@ import { Strike } from "@tiptap/extension-strike";
import { TableKit } from "@tiptap/extension-table"; import { TableKit } from "@tiptap/extension-table";
import { Text } from "@tiptap/extension-text"; import { Text } from "@tiptap/extension-text";
import { Underline } from "@tiptap/extension-underline"; import { Underline } from "@tiptap/extension-underline";
import { Dropcursor, Gapcursor, UndoRedo } from "@tiptap/extensions"; import { Dropcursor, UndoRedo } from "@tiptap/extensions";
import { type Content, EditorContent, useEditor, useEditorState } from "@tiptap/react"; import { type Content, EditorContent, useEditor, useEditorState } from "@tiptap/react";
import { type ComponentProps, useEffect, useRef } from "react"; import { type ComponentProps, useEffect, useRef } from "react";
import { tv } from "tailwind-variants"; import { tv } from "tailwind-variants";
@@ -59,7 +59,6 @@ const extensions = [
color: "#0081f1", color: "#0081f1",
width: 2, width: 2,
}), }),
Gapcursor,
UndoRedo, UndoRedo,
TableKit.configure({ TableKit.configure({
table: { resizable: true }, table: { resizable: true },

View File

@@ -12,7 +12,7 @@ import {
useFloatingRootContext, useFloatingRootContext,
useInteractions, useInteractions,
} from "@floating-ui/react"; } from "@floating-ui/react";
import { BroomIcon, CircleIcon, DotsThreeCircleVerticalIcon, IntersectIcon } from "@phosphor-icons/react"; import { BroomIcon, CircleIcon, DotsThreeCircleVerticalIcon, IntersectIcon, SplitHorizontalIcon } from "@phosphor-icons/react";
import { TextSelection } from "@tiptap/pm/state"; import { TextSelection } from "@tiptap/pm/state";
import { cellAround, CellSelection, TableMap } from "@tiptap/pm/tables"; import { cellAround, CellSelection, TableMap } from "@tiptap/pm/tables";
import { type useEditor, useEditorState } from "@tiptap/react"; import { type useEditor, useEditorState } from "@tiptap/react";
@@ -263,6 +263,32 @@ export function TableCellMenu({ editor }: TableCellMenuProps) {
setMenuOpen(false); setMenuOpen(false);
}; };
const hasSpannedCell = (): boolean => {
const { selection } = editor.state;
if (selection instanceof CellSelection) {
let found = false;
selection.forEachCell((node) => {
if (node.attrs.colspan > 1 || node.attrs.rowspan > 1) {
found = true;
}
});
return found;
}
const $pos = editor.state.doc.resolve(selection.from);
const cell = cellAround($pos);
if (!cell) return false;
const cellNode = editor.state.doc.nodeAt(cell.pos);
if (!cellNode) return false;
return cellNode.attrs.colspan > 1 || cellNode.attrs.rowspan > 1;
};
const handleSplitCell = () => {
editor.chain().focus().splitCell().run();
setMenuOpen(false);
};
const handleClearContents = () => { const handleClearContents = () => {
const { state, dispatch } = editor.view; const { state, dispatch } = editor.view;
const { selection, schema } = state; const { selection, schema } = state;
@@ -346,6 +372,12 @@ export function TableCellMenu({ editor }: TableCellMenuProps) {
<IntersectIcon size={16} weight="bold" /> <IntersectIcon size={16} weight="bold" />
Merge cells Merge cells
</MenuButton> </MenuButton>
{hasSpannedCell() && (
<MenuButton onClick={handleSplitCell}>
<SplitHorizontalIcon size={16} weight="bold" />
Split cells
</MenuButton>
)}
<MenuButton onClick={handleClearContents}> <MenuButton onClick={handleClearContents}>
<BroomIcon size={16} weight="bold" /> <BroomIcon size={16} weight="bold" />
Clear contents Clear contents