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 { defineExtension } from './extension'
import TableHandle from './table-handle'
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-scroll">
          <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" />
          <TableHandle />
        </div>
      </div>
    </ProseKit>
  )
}
const defaultContent = `
<table><tbody>
  <tr>
    <td>A1</td>
    <td>B1</td>
    <td>C1</td>
    <td>D1</td>
  </tr>
  <tr>
    <td>A2</td>
    <td>B2</td>
    <td>C2</td>
    <td>D2</td>
  </tr>
</tbody></table>
`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>import type { Editor } from 'prosekit/core'
import { useEditorDerivedValue } from 'prosekit/preact'
import {
  TableHandleColumnRoot,
  TableHandleColumnTrigger,
  TableHandleDragPreview,
  TableHandleDropIndicator,
  TableHandlePopoverContent,
  TableHandlePopoverItem,
  TableHandleRoot,
  TableHandleRowRoot,
  TableHandleRowTrigger,
} from 'prosekit/preact/table-handle'
import type { EditorExtension } from './extension'
function getTableHandleState(editor: Editor<EditorExtension>) {
  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 { defineExtension } from './extension'
import TableHandle from './table-handle'
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-scroll">
          <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>
  )
}
const defaultContent = `
<table><tbody>
  <tr>
    <td>A1</td>
    <td>B1</td>
    <td>C1</td>
    <td>D1</td>
  </tr>
  <tr>
    <td>A2</td>
    <td>B2</td>
    <td>C2</td>
    <td>D2</td>
  </tr>
</tbody></table>
`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>import type { Editor } from 'prosekit/core'
import { useEditorDerivedValue } from 'prosekit/react'
import {
  TableHandleColumnRoot,
  TableHandleColumnTrigger,
  TableHandleDragPreview,
  TableHandleDropIndicator,
  TableHandlePopoverContent,
  TableHandlePopoverItem,
  TableHandleRoot,
  TableHandleRowRoot,
  TableHandleRowTrigger,
} from 'prosekit/react/table-handle'
import type { EditorExtension } from './extension'
function getTableHandleState(editor: Editor<EditorExtension>) {
  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/solid'
import { defineExtension } from './extension'
import TableHandle from './table-handle'
export default function Editor() {
  const editor = createEditor({ extension: defineExtension(), defaultContent })
  return (
    <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-scroll">
          <div ref={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" />
          <TableHandle />
        </div>
      </div>
    </ProseKit>
  )
}
const defaultContent = `
<table><tbody>
  <tr>
    <td>A1</td>
    <td>B1</td>
    <td>C1</td>
    <td>D1</td>
  </tr>
  <tr>
    <td>A2</td>
    <td>B2</td>
    <td>C2</td>
    <td>D2</td>
  </tr>
</tbody></table>
`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>import { useEditor } from 'prosekit/solid'
import {
  TableHandleColumnRoot,
  TableHandleColumnTrigger,
  TableHandleDragPreview,
  TableHandleDropIndicator,
  TableHandlePopoverContent,
  TableHandlePopoverItem,
  TableHandleRoot,
  TableHandleRowRoot,
  TableHandleRowTrigger,
} from 'prosekit/solid/table-handle'
import type { EditorExtension } from './extension'
export default function TableHandle() {
  const editor = useEditor<EditorExtension>({ update: true })
  return (
    <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">
          {editor().commands.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={() => editor().commands.addTableColumnBefore()}
            >
              <span>Insert Left</span>
            </TableHandlePopoverItem>
          )}
          {editor().commands.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={() => editor().commands.addTableColumnAfter()}
            >
              <span>Insert Right</span>
            </TableHandlePopoverItem>
          )}
          {editor().commands.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={() => editor().commands.deleteCellSelection()}
            >
              <span>Clear Contents</span>
              <span class="text-xs tracking-widest text-gray-500 dark:text-gray-500">Del</span>
            </TableHandlePopoverItem>
          )}
          {editor().commands.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={() => editor().commands.deleteTableColumn()}
            >
              <span>Delete Column</span>
            </TableHandlePopoverItem>
          )}
          {editor().commands.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"
              attr:data-danger
              onSelect={() => editor().commands.deleteTable()}
            >
              <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">
          {editor().commands.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={() => editor().commands.addTableRowAbove()}
            >
              <span>Insert Above</span>
            </TableHandlePopoverItem>
          )}
          {editor().commands.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={() => editor().commands.addTableRowBelow()}
            >
              <span>Insert Below</span>
            </TableHandlePopoverItem>
          )}
          {editor().commands.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={() => editor().commands.deleteCellSelection()}
            >
              <span>Clear Contents</span>
              <span class="text-xs tracking-widest text-gray-500 dark:text-gray-500">Del</span>
            </TableHandlePopoverItem>
          )}
          {editor().commands.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={() => editor().commands.deleteTableRow()}
            >
              <span>Delete Row</span>
            </TableHandlePopoverItem>
          )}
          {editor().commands.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"
              attr:data-danger
              onSelect={() => editor().commands.deleteTable()}
            >
              <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 { defineExtension } from './extension'
import TableHandle from './table-handle.svelte'
const defaultContent = `
<table><tbody>
  <tr>
    <td>A1</td>
    <td>B1</td>
    <td>C1</td>
    <td>D1</td>
  </tr>
  <tr>
    <td>A2</td>
    <td>B2</td>
    <td>C2</td>
    <td>D2</td>
  </tr>
</tbody></table>
`
const editor = createEditor({ extension: defineExtension(), defaultContent })
const mount = (element: HTMLElement) => {
  editor.mount(element)
  return { destroy: () => editor.unmount() }
}
</script>
<ProseKit {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-scroll">
      <div use: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><script lang="ts">
import { useEditor } from 'prosekit/svelte'
import {
  TableHandleColumnRoot,
  TableHandleColumnTrigger,
  TableHandleDragPreview,
  TableHandleDropIndicator,
  TableHandlePopoverContent,
  TableHandlePopoverItem,
  TableHandleRoot,
  TableHandleRowRoot,
  TableHandleRowTrigger,
} from 'prosekit/svelte/table-handle'
import type { EditorExtension } from './extension'
const editor = useEditor<EditorExtension>({ update: true })
</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 $editor.commands.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={$editor.commands.addTableColumnBefore}
        >
          <span>Insert Left</span>
        </TableHandlePopoverItem>
      {/if}
      {#if $editor.commands.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={$editor.commands.addTableColumnAfter}
        >
          <span>Insert Right</span>
        </TableHandlePopoverItem>
      {/if}
      {#if $editor.commands.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={$editor.commands.deleteCellSelection}
        >
          <span>Clear Contents</span>
          <span class="text-xs tracking-widest text-gray-500 dark:text-gray-500">Del</span>
        </TableHandlePopoverItem>
      {/if}
      {#if $editor.commands.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={$editor.commands.deleteTableColumn}
        >
          <span>Delete Column</span>
        </TableHandlePopoverItem>
      {/if}
      {#if $editor.commands.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={$editor.commands.deleteTable}
        >
          <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 $editor.commands.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={$editor.commands.addTableRowAbove}
        >
          <span>Insert Above</span>
        </TableHandlePopoverItem>
      {/if}
      {#if $editor.commands.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={$editor.commands.addTableRowBelow}
        >
          <span>Insert Below</span>
        </TableHandlePopoverItem>
      {/if}
      {#if $editor.commands.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={$editor.commands.deleteCellSelection}
        >
          <span>Clear Contents</span>
          <span class="text-xs tracking-widest text-gray-500 dark:text-gray-500">Del</span>
        </TableHandlePopoverItem>
      {/if}
      {#if $editor.commands.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={$editor.commands.deleteTableRow}
        >
          <span>Delete Row</span>
        </TableHandlePopoverItem>
      {/if}
      {#if $editor.commands.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={$editor.commands.deleteTable}
        >
          <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 { defineExtension } from './extension'
import TableHandle from './table-handle.vue'
const defaultContent = `
<table><tbody>
  <tr>
    <td>A1</td>
    <td>B1</td>
    <td>C1</td>
    <td>D1</td>
  </tr>
  <tr>
    <td>A2</td>
    <td>B2</td>
    <td>C2</td>
    <td>D2</td>
  </tr>
</tbody></table>
`
const editor = createEditor({ extension: defineExtension(), 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-scroll">
        <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" />
      </div>
      <TableHandle />
    </div>
  </ProseKit>
</template>import { defineBaseKeymap } from 'prosekit/core'
import { union } from 'prosekit/core'
import { defineDoc } from 'prosekit/extensions/doc'
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(),
  )
}
export type EditorExtension = ReturnType<typeof defineExtension><script setup lang="ts">
import { useEditor } from 'prosekit/vue'
import {
  TableHandleColumnRoot,
  TableHandleColumnTrigger,
  TableHandleDragPreview,
  TableHandleDropIndicator,
  TableHandlePopoverContent,
  TableHandlePopoverItem,
  TableHandleRoot,
  TableHandleRowRoot,
  TableHandleRowTrigger,
} from 'prosekit/vue/table-handle'
import type { EditorExtension } from './extension'
const editor = useEditor<EditorExtension>({ update: true })
</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="editor.commands.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="editor.commands.addTableColumnBefore"
        >
          <span>Insert Left</span>
        </TableHandlePopoverItem>
        <TableHandlePopoverItem
          v-if="editor.commands.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="editor.commands.addTableColumnAfter"
        >
          <span>Insert Right</span>
        </TableHandlePopoverItem>
        <TableHandlePopoverItem
          v-if="editor.commands.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="editor.commands.deleteCellSelection"
        >
          <span>Clear Contents</span>
          <span class="text-xs tracking-widest text-gray-500 dark:text-gray-500">Del</span>
        </TableHandlePopoverItem>
        <TableHandlePopoverItem
          v-if="editor.commands.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="editor.commands.deleteTableColumn"
        >
          <span>Delete Column</span>
        </TableHandlePopoverItem>
        <TableHandlePopoverItem
          v-if="editor.commands.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="editor.commands.deleteTable"
        >
          <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="editor.commands.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="editor.commands.addTableRowAbove"
        >
          <span>Insert Above</span>
        </TableHandlePopoverItem>
        <TableHandlePopoverItem
          v-if="editor.commands.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="editor.commands.addTableRowBelow"
        >
          <span>Insert Below</span>
        </TableHandlePopoverItem>
        <TableHandlePopoverItem
          v-if="editor.commands.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="editor.commands.deleteCellSelection"
        >
          <span>Clear Contents</span>
          <span class="text-xs tracking-widest text-gray-500 dark:text-gray-500">Del</span>
        </TableHandlePopoverItem>
        <TableHandlePopoverItem
          v-if="editor.commands.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="editor.commands.deleteTableRow"
        >
          <span>Delete Row</span>
        </TableHandlePopoverItem>
        <TableHandlePopoverItem
          v-if="editor.commands.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="editor.commands.deleteTable"
        >
          <span>Delete Table</span>
        </TableHandlePopoverItem>
      </TableHandlePopoverContent>
    </TableHandleRowRoot>
  </TableHandleRoot>
</template>import { defineTable function defineTable(): TableExtensionextension const extension: TableExtensiondefineTable function defineTable(): TableExtensionCommands
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];
}>
insertTable insertTable: CommandAction
(options: InsertTableOptions) => boolean
row InsertTableOptions.row: numberThe number of rows in the table.col InsertTableOptions.col: numberThe number of columns in the table.header InsertTableOptions.header?: boolean | undefinedWhether the table has a header row.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];
}>
exitTable exitTable: CommandAction
() => boolean
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];
}>
selectTable selectTable: CommandAction
(options?: SelectTableOptions | undefined) => boolean
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];
}>
selectTableCell selectTableCell: CommandAction
(options?: SelectTableCellOptions | undefined) => boolean
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];
}>
selectTableColumn selectTableColumn: CommandAction
(options?: SelectTableColumnOptions | undefined) => boolean
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];
}>
selectTableRow selectTableRow: CommandAction
(options?: SelectTableRowOptions | undefined) => boolean
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];
}>
addTableColumnBefore addTableColumnBefore: CommandAction
() => boolean
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];
}>
addTableColumnAfter addTableColumnAfter: CommandAction
() => boolean
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];
}>
addTableRowAbove addTableRowAbove: CommandAction
() => boolean
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];
}>
addTableRowBelow addTableRowBelow: CommandAction
() => boolean
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];
}>
deleteTable deleteTable: CommandAction
() => boolean
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];
}>
deleteTableColumn deleteTableColumn: CommandAction
() => boolean
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];
}>
deleteTableRow deleteTableRow: CommandAction
() => boolean
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];
}>
deleteCellSelection deleteCellSelection: CommandAction
() => boolean
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];
}>
mergeTableCells mergeTableCells: CommandAction
() => boolean
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];
}>
splitTableCell splitTableCell: CommandAction
() => boolean
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];
}>
moveTableColumn moveTableColumn: CommandAction
(options: MoveTableColumnOptions) => boolean
from MoveTableColumnOptions.from: numberThe source column index to move from.to MoveTableColumnOptions.to: numberThe destination column index to move to.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];
}>
moveTableRow moveTableRow: CommandAction
(options: MoveTableRowOptions) => boolean
from MoveTableRowOptions.from: numberThe source row index to move from.to MoveTableRowOptions.to: numberThe destination row index to move to.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(): PlainExtensionextension const extension: PlainExtensiondefineTablePlugins function defineTablePlugins(): PlainExtensionComponents
Section titled “Components”TableHandle
Section titled “TableHandle”You can use the TableHandle component to control the table.