Table
The table extension adds support for creating and editing tables in your document. It provides a set of commands and utilities for working with table structures.
import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import { useMemo } from 'preact/hooks'
import { createEditor } from 'prosekit/core'
import { ProseKit } from 'prosekit/preact'
import { defaultContent } from '../../sample/sample-doc-table'
import { TableHandle } from '../../ui/table-handle'
import { defineExtension } from './extension'
export default function Editor() {
const editor = useMemo(() => {
const extension = defineExtension()
return createEditor({ extension, defaultContent })
}, [])
return (
<ProseKit editor={editor}>
<div className="box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 dark:border-gray-700 shadow-sm flex flex-col bg-white dark:bg-gray-950 text-black dark:text-white">
<div className="relative w-full flex-1 box-border overflow-y-auto">
<div ref={editor.mount} className="ProseMirror box-border min-h-full px-[max(4rem,calc(50%-20rem))] py-8 outline-hidden outline-0 [&_span[data-mention=user]]:text-blue-500 [&_span[data-mention=tag]]:text-violet-500"></div>
<TableHandle />
</div>
</div>
</ProseKit>
)
}import {
defineBaseKeymap,
defineHistory,
union,
} from 'prosekit/core'
import { defineDoc } from 'prosekit/extensions/doc'
import { defineGapCursor } from 'prosekit/extensions/gap-cursor'
import { defineParagraph } from 'prosekit/extensions/paragraph'
import { defineTable } from 'prosekit/extensions/table'
import { defineText } from 'prosekit/extensions/text'
export function defineExtension() {
return union(
defineBaseKeymap(),
defineDoc(),
defineText(),
defineParagraph(),
defineTable(),
defineHistory(),
defineGapCursor(),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>export { default as ExampleEditor } from './editor'import type { NodeJSON } from 'prosekit/core'
export const defaultContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'table',
content: [
{
type: 'tableRow',
content: [
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'A1',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'B1',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'C1',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'D1',
},
],
},
],
},
],
},
{
type: 'tableRow',
content: [
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'A2',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'B2',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'C2',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'D2',
},
],
},
],
},
],
},
],
},
],
}export { default as TableHandle } from './table-handle'import type { Editor } from 'prosekit/core'
import type { TableExtension } from 'prosekit/extensions/table'
import { useEditorDerivedValue } from 'prosekit/preact'
import {
TableHandleColumnRoot,
TableHandleColumnTrigger,
TableHandleDragPreview,
TableHandleDropIndicator,
TableHandlePopoverContent,
TableHandlePopoverItem,
TableHandleRoot,
TableHandleRowRoot,
TableHandleRowTrigger,
} from 'prosekit/preact/table-handle'
function getTableHandleState(editor: Editor<TableExtension>) {
return {
addTableColumnBefore: {
canExec: editor.commands.addTableColumnBefore.canExec(),
command: () => editor.commands.addTableColumnBefore(),
},
addTableColumnAfter: {
canExec: editor.commands.addTableColumnAfter.canExec(),
command: () => editor.commands.addTableColumnAfter(),
},
deleteCellSelection: {
canExec: editor.commands.deleteCellSelection.canExec(),
command: () => editor.commands.deleteCellSelection(),
},
deleteTableColumn: {
canExec: editor.commands.deleteTableColumn.canExec(),
command: () => editor.commands.deleteTableColumn(),
},
addTableRowAbove: {
canExec: editor.commands.addTableRowAbove.canExec(),
command: () => editor.commands.addTableRowAbove(),
},
addTableRowBelow: {
canExec: editor.commands.addTableRowBelow.canExec(),
command: () => editor.commands.addTableRowBelow(),
},
deleteTableRow: {
canExec: editor.commands.deleteTableRow.canExec(),
command: () => editor.commands.deleteTableRow(),
},
deleteTable: {
canExec: editor.commands.deleteTable.canExec(),
command: () => editor.commands.deleteTable(),
},
}
}
export default function TableHandle() {
const state = useEditorDerivedValue(getTableHandleState)
return (
<TableHandleRoot className="contents">
<TableHandleDragPreview />
<TableHandleDropIndicator />
<TableHandleColumnRoot className="h-[1.2em] w-[1.5em] translate-y-[80%] flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-500/50 border border-gray-200 dark:border-gray-800 border-solid p-0 overflow-hidden duration-150 transition-discrete transition data-[state=closed]:opacity-0 starting:opacity-0 opacity-100 data-[state=closed]:scale-95 starting:scale-95 scale-100">
<TableHandleColumnTrigger className="flex items-center justify-center">
<div className="i-lucide-grip-horizontal size-5 block"></div>
</TableHandleColumnTrigger>
<TableHandlePopoverContent className="relative block max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 z-10 box-border rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg [&:not([data-state])]:hidden">
{state.addTableColumnBefore.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.addTableColumnBefore.command}
>
<span>Insert Left</span>
</TableHandlePopoverItem>
)}
{state.addTableColumnAfter.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.addTableColumnAfter.command}
>
<span>Insert Right</span>
</TableHandlePopoverItem>
)}
{state.deleteCellSelection.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.deleteCellSelection.command}
>
<span>Clear Contents</span>
<span className="text-xs tracking-widest text-gray-500 dark:text-gray-500">Del</span>
</TableHandlePopoverItem>
)}
{state.deleteTableColumn.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.deleteTableColumn.command}
>
<span>Delete Column</span>
</TableHandlePopoverItem>
)}
{state.deleteTable.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
data-danger=""
onSelect={state.deleteTable.command}
>
<span>Delete Table</span>
</TableHandlePopoverItem>
)}
</TableHandlePopoverContent>
</TableHandleColumnRoot>
<TableHandleRowRoot className="h-[1.5em] w-[1.2em] translate-x-[80%] flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-500/50 border border-gray-200 dark:border-gray-800 border-solid p-0 overflow-hidden duration-150 transition-discrete transition data-[state=closed]:opacity-0 starting:opacity-0 opacity-100 data-[state=closed]:scale-95 starting:scale-95 scale-100">
<TableHandleRowTrigger className="flex items-center justify-center">
<div className="i-lucide-grip-vertical size-5 block"></div>
</TableHandleRowTrigger>
<TableHandlePopoverContent className="relative block max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 z-10 box-border rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg [&:not([data-state])]:hidden">
{state.addTableRowAbove.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.addTableRowAbove.command}
>
<span>Insert Above</span>
</TableHandlePopoverItem>
)}
{state.addTableRowBelow.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.addTableRowBelow.command}
>
<span>Insert Below</span>
</TableHandlePopoverItem>
)}
{state.deleteCellSelection.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.deleteCellSelection.command}
>
<span>Clear Contents</span>
<span className="text-xs tracking-widest text-gray-500 dark:text-gray-500">Del</span>
</TableHandlePopoverItem>
)}
{state.deleteTableRow.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.deleteTableRow.command}
>
<span>Delete Row</span>
</TableHandlePopoverItem>
)}
{state.deleteTable.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
data-danger=""
onSelect={state.deleteTable.command}
>
<span>Delete Table</span>
</TableHandlePopoverItem>
)}
</TableHandlePopoverContent>
</TableHandleRowRoot>
</TableHandleRoot>
)
}import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import { createEditor } from 'prosekit/core'
import { ProseKit } from 'prosekit/react'
import { useMemo } from 'react'
import { defaultContent } from '../../sample/sample-doc-table'
import { TableHandle } from '../../ui/table-handle'
import { defineExtension } from './extension'
export default function Editor() {
const editor = useMemo(() => {
const extension = defineExtension()
return createEditor({ extension, defaultContent })
}, [])
return (
<ProseKit editor={editor}>
<div className="box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 dark:border-gray-700 shadow-sm flex flex-col bg-white dark:bg-gray-950 text-black dark:text-white">
<div className="relative w-full flex-1 box-border overflow-y-auto">
<div ref={editor.mount} className="ProseMirror box-border min-h-full px-[max(4rem,calc(50%-20rem))] py-8 outline-hidden outline-0 [&_span[data-mention=user]]:text-blue-500 [&_span[data-mention=tag]]:text-violet-500"></div>
<TableHandle />
</div>
</div>
</ProseKit>
)
}import {
defineBaseKeymap,
defineHistory,
union,
} from 'prosekit/core'
import { defineDoc } from 'prosekit/extensions/doc'
import { defineGapCursor } from 'prosekit/extensions/gap-cursor'
import { defineParagraph } from 'prosekit/extensions/paragraph'
import { defineTable } from 'prosekit/extensions/table'
import { defineText } from 'prosekit/extensions/text'
export function defineExtension() {
return union(
defineBaseKeymap(),
defineDoc(),
defineText(),
defineParagraph(),
defineTable(),
defineHistory(),
defineGapCursor(),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>'use client'
export { default as ExampleEditor } from './editor'import type { NodeJSON } from 'prosekit/core'
export const defaultContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'table',
content: [
{
type: 'tableRow',
content: [
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'A1',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'B1',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'C1',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'D1',
},
],
},
],
},
],
},
{
type: 'tableRow',
content: [
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'A2',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'B2',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'C2',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'D2',
},
],
},
],
},
],
},
],
},
],
}export { default as TableHandle } from './table-handle'import type { Editor } from 'prosekit/core'
import type { TableExtension } from 'prosekit/extensions/table'
import { useEditorDerivedValue } from 'prosekit/react'
import {
TableHandleColumnRoot,
TableHandleColumnTrigger,
TableHandleDragPreview,
TableHandleDropIndicator,
TableHandlePopoverContent,
TableHandlePopoverItem,
TableHandleRoot,
TableHandleRowRoot,
TableHandleRowTrigger,
} from 'prosekit/react/table-handle'
function getTableHandleState(editor: Editor<TableExtension>) {
return {
addTableColumnBefore: {
canExec: editor.commands.addTableColumnBefore.canExec(),
command: () => editor.commands.addTableColumnBefore(),
},
addTableColumnAfter: {
canExec: editor.commands.addTableColumnAfter.canExec(),
command: () => editor.commands.addTableColumnAfter(),
},
deleteCellSelection: {
canExec: editor.commands.deleteCellSelection.canExec(),
command: () => editor.commands.deleteCellSelection(),
},
deleteTableColumn: {
canExec: editor.commands.deleteTableColumn.canExec(),
command: () => editor.commands.deleteTableColumn(),
},
addTableRowAbove: {
canExec: editor.commands.addTableRowAbove.canExec(),
command: () => editor.commands.addTableRowAbove(),
},
addTableRowBelow: {
canExec: editor.commands.addTableRowBelow.canExec(),
command: () => editor.commands.addTableRowBelow(),
},
deleteTableRow: {
canExec: editor.commands.deleteTableRow.canExec(),
command: () => editor.commands.deleteTableRow(),
},
deleteTable: {
canExec: editor.commands.deleteTable.canExec(),
command: () => editor.commands.deleteTable(),
},
}
}
export default function TableHandle() {
const state = useEditorDerivedValue(getTableHandleState)
return (
<TableHandleRoot className="contents">
<TableHandleDragPreview />
<TableHandleDropIndicator />
<TableHandleColumnRoot className="h-[1.2em] w-[1.5em] translate-y-[80%] flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-500/50 border border-gray-200 dark:border-gray-800 border-solid p-0 overflow-hidden duration-150 transition-discrete transition data-[state=closed]:opacity-0 starting:opacity-0 opacity-100 data-[state=closed]:scale-95 starting:scale-95 scale-100">
<TableHandleColumnTrigger className="flex items-center justify-center">
<div className="i-lucide-grip-horizontal size-5 block"></div>
</TableHandleColumnTrigger>
<TableHandlePopoverContent className="relative block max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 z-10 box-border rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg [&:not([data-state])]:hidden">
{state.addTableColumnBefore.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.addTableColumnBefore.command}
>
<span>Insert Left</span>
</TableHandlePopoverItem>
)}
{state.addTableColumnAfter.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.addTableColumnAfter.command}
>
<span>Insert Right</span>
</TableHandlePopoverItem>
)}
{state.deleteCellSelection.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.deleteCellSelection.command}
>
<span>Clear Contents</span>
<span className="text-xs tracking-widest text-gray-500 dark:text-gray-500">Del</span>
</TableHandlePopoverItem>
)}
{state.deleteTableColumn.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.deleteTableColumn.command}
>
<span>Delete Column</span>
</TableHandlePopoverItem>
)}
{state.deleteTable.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
data-danger=""
onSelect={state.deleteTable.command}
>
<span>Delete Table</span>
</TableHandlePopoverItem>
)}
</TableHandlePopoverContent>
</TableHandleColumnRoot>
<TableHandleRowRoot className="h-[1.5em] w-[1.2em] translate-x-[80%] flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-500/50 border border-gray-200 dark:border-gray-800 border-solid p-0 overflow-hidden duration-150 transition-discrete transition data-[state=closed]:opacity-0 starting:opacity-0 opacity-100 data-[state=closed]:scale-95 starting:scale-95 scale-100">
<TableHandleRowTrigger className="flex items-center justify-center">
<div className="i-lucide-grip-vertical size-5 block"></div>
</TableHandleRowTrigger>
<TableHandlePopoverContent className="relative block max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 z-10 box-border rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg [&:not([data-state])]:hidden">
{state.addTableRowAbove.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.addTableRowAbove.command}
>
<span>Insert Above</span>
</TableHandlePopoverItem>
)}
{state.addTableRowBelow.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.addTableRowBelow.command}
>
<span>Insert Below</span>
</TableHandlePopoverItem>
)}
{state.deleteCellSelection.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.deleteCellSelection.command}
>
<span>Clear Contents</span>
<span className="text-xs tracking-widest text-gray-500 dark:text-gray-500">Del</span>
</TableHandlePopoverItem>
)}
{state.deleteTableRow.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={state.deleteTableRow.command}
>
<span>Delete Row</span>
</TableHandlePopoverItem>
)}
{state.deleteTable.canExec && (
<TableHandlePopoverItem
className="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
data-danger=""
onSelect={state.deleteTable.command}
>
<span>Delete Table</span>
</TableHandlePopoverItem>
)}
</TableHandlePopoverContent>
</TableHandleRowRoot>
</TableHandleRoot>
)
}<script lang="ts">
import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import { createEditor } from 'prosekit/core'
import { ProseKit } from 'prosekit/svelte'
import { defaultContent } from '../../sample/sample-doc-table'
import { TableHandle } from '../../ui/table-handle'
import { defineExtension } from './extension'
const extension = defineExtension()
const editor = createEditor({ extension, defaultContent })
</script>
<ProseKit editor={editor}>
<div class="box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 dark:border-gray-700 shadow-sm flex flex-col bg-white dark:bg-gray-950 text-black dark:text-white">
<div class="relative w-full flex-1 box-border overflow-y-auto">
<div use:editor.mount class="ProseMirror box-border min-h-full px-[max(4rem,calc(50%-20rem))] py-8 outline-hidden outline-0 [&_span[data-mention=user]]:text-blue-500 [&_span[data-mention=tag]]:text-violet-500"></div>
<TableHandle />
</div>
</div>
</ProseKit>import {
defineBaseKeymap,
defineHistory,
union,
} from 'prosekit/core'
import { defineDoc } from 'prosekit/extensions/doc'
import { defineGapCursor } from 'prosekit/extensions/gap-cursor'
import { defineParagraph } from 'prosekit/extensions/paragraph'
import { defineTable } from 'prosekit/extensions/table'
import { defineText } from 'prosekit/extensions/text'
export function defineExtension() {
return union(
defineBaseKeymap(),
defineDoc(),
defineText(),
defineParagraph(),
defineTable(),
defineHistory(),
defineGapCursor(),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>export { default as ExampleEditor } from './editor.svelte'import type { NodeJSON } from 'prosekit/core'
export const defaultContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'table',
content: [
{
type: 'tableRow',
content: [
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'A1',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'B1',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'C1',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'D1',
},
],
},
],
},
],
},
{
type: 'tableRow',
content: [
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'A2',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'B2',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'C2',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'D2',
},
],
},
],
},
],
},
],
},
],
}export { default as TableHandle } from './table-handle.svelte'<script lang="ts">
import type { Editor } from 'prosekit/core'
import type { TableExtension } from 'prosekit/extensions/table'
import { useEditorDerivedValue } from 'prosekit/svelte'
import {
TableHandleColumnRoot,
TableHandleColumnTrigger,
TableHandleDragPreview,
TableHandleDropIndicator,
TableHandlePopoverContent,
TableHandlePopoverItem,
TableHandleRoot,
TableHandleRowRoot,
TableHandleRowTrigger,
} from 'prosekit/svelte/table-handle'
function getTableHandleState(editor: Editor<TableExtension>) {
return {
addTableColumnBefore: {
canExec: editor.commands.addTableColumnBefore.canExec(),
command: () => editor.commands.addTableColumnBefore(),
},
addTableColumnAfter: {
canExec: editor.commands.addTableColumnAfter.canExec(),
command: () => editor.commands.addTableColumnAfter(),
},
deleteCellSelection: {
canExec: editor.commands.deleteCellSelection.canExec(),
command: () => editor.commands.deleteCellSelection(),
},
deleteTableColumn: {
canExec: editor.commands.deleteTableColumn.canExec(),
command: () => editor.commands.deleteTableColumn(),
},
addTableRowAbove: {
canExec: editor.commands.addTableRowAbove.canExec(),
command: () => editor.commands.addTableRowAbove(),
},
addTableRowBelow: {
canExec: editor.commands.addTableRowBelow.canExec(),
command: () => editor.commands.addTableRowBelow(),
},
deleteTableRow: {
canExec: editor.commands.deleteTableRow.canExec(),
command: () => editor.commands.deleteTableRow(),
},
deleteTable: {
canExec: editor.commands.deleteTable.canExec(),
command: () => editor.commands.deleteTable(),
},
}
}
const state = useEditorDerivedValue(getTableHandleState)
</script>
<TableHandleRoot class="contents">
<TableHandleDragPreview />
<TableHandleDropIndicator />
<TableHandleColumnRoot class="h-[1.2em] w-[1.5em] translate-y-[80%] flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-500/50 border border-gray-200 dark:border-gray-800 border-solid p-0 overflow-hidden duration-150 transition-discrete transition data-[state=closed]:opacity-0 starting:opacity-0 opacity-100 data-[state=closed]:scale-95 starting:scale-95 scale-100">
<TableHandleColumnTrigger class="flex items-center justify-center">
<div class="i-lucide-grip-horizontal size-5 block"></div>
</TableHandleColumnTrigger>
<TableHandlePopoverContent class="relative block max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 z-10 box-border rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg [&:not([data-state])]:hidden">
{#if $state.addTableColumnBefore.canExec}
<TableHandlePopoverItem
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={$state.addTableColumnBefore.command}
>
<span>Insert Left</span>
</TableHandlePopoverItem>
{/if}
{#if $state.addTableColumnAfter.canExec}
<TableHandlePopoverItem
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={$state.addTableColumnAfter.command}
>
<span>Insert Right</span>
</TableHandlePopoverItem>
{/if}
{#if $state.deleteCellSelection.canExec}
<TableHandlePopoverItem
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={$state.deleteCellSelection.command}
>
<span>Clear Contents</span>
<span class="text-xs tracking-widest text-gray-500 dark:text-gray-500">Del</span>
</TableHandlePopoverItem>
{/if}
{#if $state.deleteTableColumn.canExec}
<TableHandlePopoverItem
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={$state.deleteTableColumn.command}
>
<span>Delete Column</span>
</TableHandlePopoverItem>
{/if}
{#if $state.deleteTable.canExec}
<TableHandlePopoverItem
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
data-danger=""
onSelect={$state.deleteTable.command}
>
<span>Delete Table</span>
</TableHandlePopoverItem>
{/if}
</TableHandlePopoverContent>
</TableHandleColumnRoot>
<TableHandleRowRoot class="h-[1.5em] w-[1.2em] translate-x-[80%] flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-500/50 border border-gray-200 dark:border-gray-800 border-solid p-0 overflow-hidden duration-150 transition-discrete transition data-[state=closed]:opacity-0 starting:opacity-0 opacity-100 data-[state=closed]:scale-95 starting:scale-95 scale-100">
<TableHandleRowTrigger class="flex items-center justify-center">
<div class="i-lucide-grip-vertical size-5 block"></div>
</TableHandleRowTrigger>
<TableHandlePopoverContent class="relative block max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 z-10 box-border rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg [&:not([data-state])]:hidden">
{#if $state.addTableRowAbove.canExec}
<TableHandlePopoverItem
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={$state.addTableRowAbove.command}
>
<span>Insert Above</span>
</TableHandlePopoverItem>
{/if}
{#if $state.addTableRowBelow.canExec}
<TableHandlePopoverItem
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={$state.addTableRowBelow.command}
>
<span>Insert Below</span>
</TableHandlePopoverItem>
{/if}
{#if $state.deleteCellSelection.canExec}
<TableHandlePopoverItem
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={$state.deleteCellSelection.command}
>
<span>Clear Contents</span>
<span class="text-xs tracking-widest text-gray-500 dark:text-gray-500">Del</span>
</TableHandlePopoverItem>
{/if}
{#if $state.deleteTableRow.canExec}
<TableHandlePopoverItem
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
onSelect={$state.deleteTableRow.command}
>
<span>Delete Row</span>
</TableHandlePopoverItem>
{/if}
{#if $state.deleteTable.canExec}
<TableHandlePopoverItem
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
data-danger=""
onSelect={$state.deleteTable.command}
>
<span>Delete Table</span>
</TableHandlePopoverItem>
{/if}
</TableHandlePopoverContent>
</TableHandleRowRoot>
</TableHandleRoot><script setup lang="ts">
import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import { createEditor } from 'prosekit/core'
import { ProseKit } from 'prosekit/vue'
import {
ref,
watchPostEffect,
} from 'vue'
import { defaultContent } from '../../sample/sample-doc-table'
import { TableHandle } from '../../ui/table-handle'
import { defineExtension } from './extension'
const extension = defineExtension()
const editor = createEditor({ extension, defaultContent })
const editorRef = ref<HTMLDivElement | null>(null)
watchPostEffect((onCleanup) => {
editor.mount(editorRef.value)
onCleanup(() => editor.unmount())
})
</script>
<template>
<ProseKit :editor="editor">
<div class="box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 dark:border-gray-700 shadow-sm flex flex-col bg-white dark:bg-gray-950 text-black dark:text-white">
<div class="relative w-full flex-1 box-border overflow-y-auto">
<div ref="editorRef" class="ProseMirror box-border min-h-full px-[max(4rem,calc(50%-20rem))] py-8 outline-hidden outline-0 [&_span[data-mention=user]]:text-blue-500 [&_span[data-mention=tag]]:text-violet-500" />
<TableHandle />
</div>
</div>
</ProseKit>
</template>import {
defineBaseKeymap,
defineHistory,
union,
} from 'prosekit/core'
import { defineDoc } from 'prosekit/extensions/doc'
import { defineGapCursor } from 'prosekit/extensions/gap-cursor'
import { defineParagraph } from 'prosekit/extensions/paragraph'
import { defineTable } from 'prosekit/extensions/table'
import { defineText } from 'prosekit/extensions/text'
export function defineExtension() {
return union(
defineBaseKeymap(),
defineDoc(),
defineText(),
defineParagraph(),
defineTable(),
defineHistory(),
defineGapCursor(),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>export { default as ExampleEditor } from './editor.vue'import type { NodeJSON } from 'prosekit/core'
export const defaultContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'table',
content: [
{
type: 'tableRow',
content: [
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'A1',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'B1',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'C1',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'D1',
},
],
},
],
},
],
},
{
type: 'tableRow',
content: [
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'A2',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'B2',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'C2',
},
],
},
],
},
{
type: 'tableCell',
attrs: {
colspan: 1,
rowspan: 1,
colwidth: null,
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'D2',
},
],
},
],
},
],
},
],
},
],
}export { default as TableHandle } from './table-handle.vue'<script setup lang="ts">
import type { Editor } from 'prosekit/core'
import type { TableExtension } from 'prosekit/extensions/table'
import { useEditorDerivedValue } from 'prosekit/vue'
import {
TableHandleColumnRoot,
TableHandleColumnTrigger,
TableHandleDragPreview,
TableHandleDropIndicator,
TableHandlePopoverContent,
TableHandlePopoverItem,
TableHandleRoot,
TableHandleRowRoot,
TableHandleRowTrigger,
} from 'prosekit/vue/table-handle'
function getTableHandleState(editor: Editor<TableExtension>) {
return {
addTableColumnBefore: {
canExec: editor.commands.addTableColumnBefore.canExec(),
command: () => editor.commands.addTableColumnBefore(),
},
addTableColumnAfter: {
canExec: editor.commands.addTableColumnAfter.canExec(),
command: () => editor.commands.addTableColumnAfter(),
},
deleteCellSelection: {
canExec: editor.commands.deleteCellSelection.canExec(),
command: () => editor.commands.deleteCellSelection(),
},
deleteTableColumn: {
canExec: editor.commands.deleteTableColumn.canExec(),
command: () => editor.commands.deleteTableColumn(),
},
addTableRowAbove: {
canExec: editor.commands.addTableRowAbove.canExec(),
command: () => editor.commands.addTableRowAbove(),
},
addTableRowBelow: {
canExec: editor.commands.addTableRowBelow.canExec(),
command: () => editor.commands.addTableRowBelow(),
},
deleteTableRow: {
canExec: editor.commands.deleteTableRow.canExec(),
command: () => editor.commands.deleteTableRow(),
},
deleteTable: {
canExec: editor.commands.deleteTable.canExec(),
command: () => editor.commands.deleteTable(),
},
}
}
const state = useEditorDerivedValue(getTableHandleState)
</script>
<template>
<TableHandleRoot class="contents">
<TableHandleDragPreview />
<TableHandleDropIndicator />
<TableHandleColumnRoot class="h-[1.2em] w-[1.5em] translate-y-[80%] flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-500/50 border border-gray-200 dark:border-gray-800 border-solid p-0 overflow-hidden duration-150 transition-discrete transition data-[state=closed]:opacity-0 starting:opacity-0 opacity-100 data-[state=closed]:scale-95 starting:scale-95 scale-100">
<TableHandleColumnTrigger class="flex items-center justify-center">
<div class="i-lucide-grip-horizontal size-5 block"></div>
</TableHandleColumnTrigger>
<TableHandlePopoverContent class="relative block max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 z-10 box-border rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg [&:not([data-state])]:hidden">
<TableHandlePopoverItem
v-if="state.addTableColumnBefore.canExec"
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
@select="state.addTableColumnBefore.command"
>
<span>Insert Left</span>
</TableHandlePopoverItem>
<TableHandlePopoverItem
v-if="state.addTableColumnAfter.canExec"
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
@select="state.addTableColumnAfter.command"
>
<span>Insert Right</span>
</TableHandlePopoverItem>
<TableHandlePopoverItem
v-if="state.deleteCellSelection.canExec"
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
@select="state.deleteCellSelection.command"
>
<span>Clear Contents</span>
<span class="text-xs tracking-widest text-gray-500 dark:text-gray-500">Del</span>
</TableHandlePopoverItem>
<TableHandlePopoverItem
v-if="state.deleteTableColumn.canExec"
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
@select="state.deleteTableColumn.command"
>
<span>Delete Column</span>
</TableHandlePopoverItem>
<TableHandlePopoverItem
v-if="state.deleteTable.canExec"
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
data-danger
@select="state.deleteTable.command"
>
<span>Delete Table</span>
</TableHandlePopoverItem>
</TableHandlePopoverContent>
</TableHandleColumnRoot>
<TableHandleRowRoot class="h-[1.5em] w-[1.2em] translate-x-[80%] flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-500/50 border border-gray-200 dark:border-gray-800 border-solid p-0 overflow-hidden duration-150 transition-discrete transition data-[state=closed]:opacity-0 starting:opacity-0 opacity-100 data-[state=closed]:scale-95 starting:scale-95 scale-100">
<TableHandleRowTrigger class="flex items-center justify-center">
<div class="i-lucide-grip-vertical size-5 block"></div>
</TableHandleRowTrigger>
<TableHandlePopoverContent class="relative block max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 z-10 box-border rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg [&:not([data-state])]:hidden">
<TableHandlePopoverItem
v-if="state.addTableRowAbove.canExec"
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
@select="state.addTableRowAbove.command"
>
<span>Insert Above</span>
</TableHandlePopoverItem>
<TableHandlePopoverItem
v-if="state.addTableRowBelow.canExec"
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
@select="state.addTableRowBelow.command"
>
<span>Insert Below</span>
</TableHandlePopoverItem>
<TableHandlePopoverItem
v-if="state.deleteCellSelection.canExec"
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
@select="state.deleteCellSelection.command"
>
<span>Clear Contents</span>
<span class="text-xs tracking-widest text-gray-500 dark:text-gray-500">Del</span>
</TableHandlePopoverItem>
<TableHandlePopoverItem
v-if="state.deleteTableRow.canExec"
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
@select="state.deleteTableRow.command"
>
<span>Delete Row</span>
</TableHandlePopoverItem>
<TableHandlePopoverItem
v-if="state.deleteTable.canExec"
class="relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-focused:bg-gray-100 dark:data-focused:bg-gray-800"
data-danger
@select="state.deleteTable.command"
>
<span>Delete Table</span>
</TableHandlePopoverItem>
</TableHandlePopoverContent>
</TableHandleRowRoot>
</TableHandleRoot>
</template>import { defineTable function defineTable(): TableExtension } from 'prosekit/extensions/table'
const extension const extension: TableExtension = defineTable function defineTable(): TableExtension ()Commands
Section titled “Commands”insertTable
Section titled “insertTable”Insert a new table.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .insertTable insertTable: CommandAction
(options: InsertTableOptions) => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ({ row InsertTableOptions.row: numberThe number of rows in the table. : 3, col InsertTableOptions.col: numberThe number of columns in the table. : 3, header InsertTableOptions.header?: boolean | undefinedWhether the table has a header row. : true })exitTable
Section titled “exitTable”Exit the table.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .exitTable exitTable: CommandAction
() => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()selectTable
Section titled “selectTable”Select the table.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .selectTable selectTable: CommandAction
(options?: SelectTableOptions | undefined) => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()selectTableCell
Section titled “selectTableCell”Select the table cell.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .selectTableCell selectTableCell: CommandAction
(options?: SelectTableCellOptions | undefined) => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()selectTableColumn
Section titled “selectTableColumn”Select the table column.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .selectTableColumn selectTableColumn: CommandAction
(options?: SelectTableColumnOptions | undefined) => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()selectTableRow
Section titled “selectTableRow”Select the table row.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .selectTableRow selectTableRow: CommandAction
(options?: SelectTableRowOptions | undefined) => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()addTableColumnBefore
Section titled “addTableColumnBefore”Add a new column before the selected column.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .addTableColumnBefore addTableColumnBefore: CommandAction
() => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()addTableColumnAfter
Section titled “addTableColumnAfter”Add a new column after the selected column.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .addTableColumnAfter addTableColumnAfter: CommandAction
() => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()addTableRowAbove
Section titled “addTableRowAbove”Add a new row above the selected row or specific position.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .addTableRowAbove addTableRowAbove: CommandAction
() => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()addTableRowBelow
Section titled “addTableRowBelow”Add a new row below the selected row or specific position.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .addTableRowBelow addTableRowBelow: CommandAction
() => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()deleteTable
Section titled “deleteTable”Delete the selected table.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .deleteTable deleteTable: CommandAction
() => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()deleteTableColumn
Section titled “deleteTableColumn”Delete the selected column or specific position.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .deleteTableColumn deleteTableColumn: CommandAction
() => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()deleteTableRow
Section titled “deleteTableRow”Delete the selected row or specific position.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .deleteTableRow deleteTableRow: CommandAction
() => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()deleteCellSelection
Section titled “deleteCellSelection”Delete the cell selection.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .deleteCellSelection deleteCellSelection: CommandAction
() => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()mergeTableCells
Section titled “mergeTableCells”Merge the selected cells.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .mergeTableCells mergeTableCells: CommandAction
() => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()splitTableCell
Section titled “splitTableCell”Split the selected cell.
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .splitTableCell splitTableCell: CommandAction
() => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()moveTableColumn
Section titled “moveTableColumn”Move a column.
// Move the second column to the first column
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .moveTableColumn moveTableColumn: CommandAction
(options: MoveTableColumnOptions) => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ({ from MoveTableColumnOptions.from: numberThe source column index to move from. : 1, to MoveTableColumnOptions.to: numberThe destination column index to move to. : 0 })moveTableRow
Section titled “moveTableRow”Move a row.
// Move the first row to the 4th row
editor const editor: Editor<TableExtension> .commands Editor<TableExtension>.commands: ToCommandAction<{
insertTable: [options: InsertTableOptions];
exitTable: [];
selectTable: [options?: SelectTableOptions];
selectTableCell: [options?: SelectTableCellOptions];
selectTableColumn: [options?: SelectTableColumnOptions];
selectTableRow: [options?: SelectTableRowOptions];
addTableColumnBefore: [];
addTableColumnAfter: [];
... 9 more ...;
moveTableColumn: [options: MoveTableColumnOptions];
}>
All
{@link
CommandAction
}
s defined by the editor. .moveTableRow moveTableRow: CommandAction
(options: MoveTableRowOptions) => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ({ from MoveTableRowOptions.from: numberThe source row index to move from. : 0, to MoveTableRowOptions.to: numberThe destination row index to move to. : 3 })Plugins
Section titled “Plugins”tableEditing and columnResizing
Section titled “tableEditing and columnResizing”These plugins are built-in plugins in prosemirror-tables.
import { defineTablePlugins function defineTablePlugins(): PlainExtension } from 'prosekit/extensions/table'
const extension const extension: PlainExtension = defineTablePlugins function defineTablePlugins(): PlainExtension ()Components
Section titled “Components”TableHandle
Section titled “TableHandle”You can use the TableHandle component to control the table.