import { type KeyboardEvent, useEffect, useRef, useState } from "react"; import type { EditorView } from "@codemirror/view"; import { insertMarkdownTable } from "./markdown-editor-commands"; interface MarkdownTableToolProps { editorView: EditorView | null; } const maximumColumns = 10; const maximumRows = 10; const minimumRows = 2; interface TableSize { columns: number; rows: number; } function clamp(value: number, minimum: number, maximum: number) { return Math.min(maximum, Math.max(minimum, value)); } export function MarkdownTableTool({ editorView }: MarkdownTableToolProps) { const [open, setOpen] = useState(false); const [size, setSize] = useState({ columns: 3, rows: 3 }); const rootRef = useRef(null); const triggerRef = useRef(null); function focusSize(nextSize: TableSize) { window.requestAnimationFrame(() => { rootRef.current ?.querySelector( `[data-table-size="${nextSize.columns}x${nextSize.rows}"]` ) ?.focus(); }); } useEffect(() => { if (!open) { return; } focusSize(size); const handlePointerDown = (event: PointerEvent) => { if ( event.target instanceof Node && !rootRef.current?.contains(event.target) ) { setOpen(false); } }; const handleKeyDown = (event: globalThis.KeyboardEvent) => { if (event.key === "Escape") { event.preventDefault(); setOpen(false); triggerRef.current?.focus(); } }; document.addEventListener("pointerdown", handlePointerDown); document.addEventListener("keydown", handleKeyDown); return () => { document.removeEventListener("pointerdown", handlePointerDown); document.removeEventListener("keydown", handleKeyDown); }; }, [open]); useEffect(() => { if (!editorView) { setOpen(false); } }, [editorView]); function moveSelection( event: KeyboardEvent, columnsDelta: number, rowsDelta: number ) { event.preventDefault(); const nextSize = { columns: clamp( size.columns + columnsDelta, 1, maximumColumns ), rows: clamp( size.rows + rowsDelta, minimumRows, maximumRows ) }; setSize(nextSize); focusSize(nextSize); } function insertTable(selectedSize: TableSize) { if (!editorView) { return; } insertMarkdownTable( editorView, selectedSize.columns, selectedSize.rows ); setOpen(false); } return (
{open ? (
{size.columns} 列 × {size.rows} 行 首行为表头
{Array.from( { length: maximumColumns * (maximumRows - minimumRows + 1) }, (_, index) => { const columns = (index % maximumColumns) + 1; const rows = Math.floor(index / maximumColumns) + minimumRows; const selected = columns <= size.columns && rows <= size.rows; const active = columns === size.columns && rows === size.rows; return (
) : null}
); }