'use client' import * as React from 'react' import type { TableAddControlsState } from '@admin/utils/editor/table-add-controls-state' import type { Editor } from '@tiptap/react' import { ACTIVE_TABLE_WRAPPER_CLASS } from '@admin/utils/editor/active-table-wrapper-class' import { appendColumnToTable } from '@admin/utils/editor/append-column-to-table' import { appendRowToTable } from '@admin/utils/editor/append-row-to-table' import { getMountedEditorView } from '@admin/utils/editor/editor-view' import { getSelectionTable } from '@admin/utils/editor/get-selection-table' import { getTableFromEventTarget } from '@admin/utils/editor/get-table-from-event-target' import { getTableFromWrapper } from '@admin/utils/editor/get-table-from-wrapper' export interface ContentEditorTableAddControls { addColumn: () => void addRow: () => void wrapper: HTMLElement } export function useContentEditorTableAddControls( editor: Editor | null ): ContentEditorTableAddControls | null { const [tableState, setTableState] = React.useState(null) const [, markEditorViewChanged] = React.useReducer((version: number) => version + 1, 0) const tableStateRef = React.useRef(null) const isEditorViewMounted = Boolean(getMountedEditorView(editor)) tableStateRef.current = tableState const setTable = React.useCallback((next: TableAddControlsState) => { setTableState((current) => { if (current?.wrapper !== next.wrapper) { current?.wrapper.classList.remove(ACTIVE_TABLE_WRAPPER_CLASS) } next.wrapper.classList.add(ACTIVE_TABLE_WRAPPER_CLASS) if (current?.pos === next.pos && current.wrapper === next.wrapper) { return current } return next }) }, []) const clearTable = React.useCallback(() => { setTableState((current) => { current?.wrapper.classList.remove(ACTIVE_TABLE_WRAPPER_CLASS) return null }) }, []) React.useEffect(() => { if (!editor) return const handleEditorViewChange = () => markEditorViewChanged() editor.on('mount', handleEditorViewChange) editor.on('unmount', handleEditorViewChange) if (getMountedEditorView(editor)) { handleEditorViewChange() } return () => { editor.off('mount', handleEditorViewChange) editor.off('unmount', handleEditorViewChange) } }, [editor]) React.useEffect(() => { const view = isEditorViewMounted ? getMountedEditorView(editor) : null if (!editor?.isEditable || !view) { clearTable() return } const editorElement = view.dom const updateFromSelection = () => { const selectedTable = getSelectionTable(editor, view) if (selectedTable) { setTable(selectedTable) return } const currentWrapper = tableStateRef.current?.wrapper if (currentWrapper?.matches(':hover')) { const currentTable = getTableFromWrapper(editor, view, currentWrapper) if (currentTable) { setTable(currentTable) return } } clearTable() } const refreshCurrentTable = () => { const current = tableStateRef.current if (!current) { updateFromSelection() return } if (!current.wrapper.isConnected || !editorElement.contains(current.wrapper)) { updateFromSelection() return } const currentTable = getTableFromWrapper(editor, view, current.wrapper) if (!currentTable) { updateFromSelection() return } setTable(currentTable) } const handlePointerOver = (event: PointerEvent) => { const hoveredTable = getTableFromEventTarget(editor, view, event.target) if (hoveredTable) { setTable(hoveredTable) return } if (!getSelectionTable(editor, view)) { clearTable() } } const handlePointerLeave = () => { if (!getSelectionTable(editor, view)) { clearTable() } } editorElement.addEventListener('pointerover', handlePointerOver) editorElement.addEventListener('pointerleave', handlePointerLeave) editor.on('selectionUpdate', updateFromSelection) editor.on('transaction', refreshCurrentTable) updateFromSelection() return () => { editorElement.removeEventListener('pointerover', handlePointerOver) editorElement.removeEventListener('pointerleave', handlePointerLeave) editor.off('selectionUpdate', updateFromSelection) editor.off('transaction', refreshCurrentTable) tableStateRef.current?.wrapper.classList.remove(ACTIVE_TABLE_WRAPPER_CLASS) } }, [clearTable, editor, isEditorViewMounted, setTable]) const addColumn = React.useCallback(() => { const tablePos = tableStateRef.current?.pos if (!editor || tablePos === undefined) return appendColumnToTable(editor, tablePos) }, [editor]) const addRow = React.useCallback(() => { const tablePos = tableStateRef.current?.pos if (!editor || tablePos === undefined) return appendRowToTable(editor, tablePos) }, [editor]) if (!editor?.isEditable || !tableState) return null return { addColumn, addRow, wrapper: tableState.wrapper } }