Files
docmost/packages/editor-ext/src/lib/table/dnd/utils.ts
T
Mirone 7d1e5bce0d feat: table row/column drag and drop (#1467)
* chore: add dev container

* feat: add drag handle when hovering cell

* feat: add column drag and drop

* feat: add support for row drag and drop

* refactor: extract preview controllers

* fix: hover issue

* refactor: add handle controller

* chore: f

* chore: remove log

* chore: remove dev files

* feat: hide other drop indicators when table dnd working

* feat: add auto scroll and bug fix

* chore: f

* fix: firefox
2025-08-31 18:53:27 +01:00

107 lines
3.1 KiB
TypeScript

import { cellAround, TableMap } from "@tiptap/pm/tables"
import { EditorView } from "@tiptap/pm/view"
export function getHoveringCell(
view: EditorView,
event: MouseEvent,
): HoveringCellInfo | undefined {
const domCell = domCellAround(event.target as HTMLElement | null)
if (!domCell) return
const { left, top, width, height } = domCell.getBoundingClientRect()
const eventPos = view.posAtCoords({
// Use the center coordinates of the cell to ensure we're within the
// selected cell. This prevents potential issues when the mouse is on the
// border of two cells.
left: left + width / 2,
top: top + height / 2,
})
if (!eventPos) return
const $cellPos = cellAround(view.state.doc.resolve(eventPos.pos))
if (!$cellPos) return
const map = TableMap.get($cellPos.node(-1))
const tableStart = $cellPos.start(-1)
const cellRect = map.findCell($cellPos.pos - tableStart)
const rowIndex = cellRect.top
const colIndex = cellRect.left
return {
rowIndex,
colIndex,
cellPos: $cellPos.pos,
rowFirstCellPos: getCellPos(map, tableStart, rowIndex, 0),
colFirstCellPos: getCellPos(map, tableStart, 0, colIndex),
}
}
function domCellAround(target: HTMLElement | null): HTMLElement | null {
while (target && target.nodeName != 'TD' && target.nodeName != 'TH') {
target = target.classList?.contains('ProseMirror')
? null
: (target.parentNode as HTMLElement | null)
}
return target
}
export interface HoveringCellInfo {
rowIndex: number
colIndex: number
cellPos: number
rowFirstCellPos: number
colFirstCellPos: number
}
function getCellPos(
map: TableMap,
tableStart: number,
rowIndex: number,
colIndex: number,
) {
const cellIndex = getCellIndex(map, rowIndex, colIndex)
const posInTable = map.map[cellIndex]
return tableStart + posInTable
}
function getCellIndex(
map: TableMap,
rowIndex: number,
colIndex: number,
): number {
return map.width * rowIndex + colIndex
}
function getTableDOMByPos(view: EditorView, pos: number): HTMLTableElement | undefined {
const dom = view.domAtPos(pos).node
if (!dom) return
const element = dom instanceof HTMLElement ? dom : dom.parentElement
const table = element?.closest('table')
return table ?? undefined
}
function getTargetFirstCellDOM(table: HTMLTableElement, index: number, direction: 'row' | 'col'): HTMLTableCellElement | undefined {
if (direction === 'row') {
const row = table.querySelectorAll('tr')[index]
const cell = row?.querySelector<HTMLTableCellElement>('th,td')
return cell ?? undefined
} else {
const row = table.querySelector('tr')
const cell = row?.querySelectorAll<HTMLTableCellElement>('th,td')[index]
return cell ?? undefined
}
}
export type DraggingDOMs = {
table: HTMLTableElement
cell: HTMLTableCellElement
}
export function getDndRelatedDOMs(view: EditorView, cellPos: number | undefined, draggingIndex: number, direction: 'row' | 'col'): DraggingDOMs | undefined {
if (cellPos == null) return
const table = getTableDOMByPos(view, cellPos)
if (!table) return
const cell = getTargetFirstCellDOM(table, draggingIndex, direction)
if (!cell) return
return { table, cell }
}