Example: table
Install this example with
shadcn:npx shadcn@latest add @prosekit/react-example-tablenpx shadcn@latest add @prosekit/preact-example-tablenpx shadcn@latest add @prosekit/svelte-example-tablenpx shadcn@latest add @prosekit/vue-example-tableimport 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import { useMemo } from 'preact/hooks'
import {
createEditor,
type NodeJSON,
} from 'prosekit/core'
import { ProseKit } from 'prosekit/preact'
import { sampleContent } from '../../sample/sample-doc-table'
import { TableHandle } from '../../ui/table-handle'
import { defineExtension } from './extension'
interface EditorProps {
initialContent?: NodeJSON
}
export default function Editor(props: EditorProps) {
const defaultContent = props.initialContent ?? sampleContent
const editor = useMemo(() => {
const extension = defineExtension()
return createEditor({ extension, defaultContent })
}, [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 sampleContent: 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,
type NodeJSON,
} from 'prosekit/core'
import { ProseKit } from 'prosekit/react'
import { useMemo } from 'react'
import { sampleContent } from '../../sample/sample-doc-table'
import { TableHandle } from '../../ui/table-handle'
import { defineExtension } from './extension'
interface EditorProps {
initialContent?: NodeJSON
}
export default function Editor(props: EditorProps) {
const defaultContent = props.initialContent ?? sampleContent
const editor = useMemo(() => {
const extension = defineExtension()
return createEditor({ extension, defaultContent })
}, [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 sampleContent: 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,
type NodeJSON,
} from 'prosekit/core'
import { ProseKit } from 'prosekit/svelte'
import { sampleContent } from '../../sample/sample-doc-table'
import { TableHandle } from '../../ui/table-handle'
import { defineExtension } from './extension'
const props: {
initialContent?: NodeJSON
} = $props()
const extension = defineExtension()
const defaultContent = props.initialContent ?? sampleContent
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 sampleContent: 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,
type NodeJSON,
} from 'prosekit/core'
import { ProseKit } from 'prosekit/vue'
import {
ref,
watchPostEffect,
} from 'vue'
import { sampleContent } from '../../sample/sample-doc-table'
import { TableHandle } from '../../ui/table-handle'
import { defineExtension } from './extension'
const props = defineProps<{
initialContent?: NodeJSON
}>()
const extension = defineExtension()
const defaultContent = props.initialContent ?? sampleContent
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 sampleContent: 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>