fix: Block Editor表格块向右拖拽溢出后往回拖拽无法删除列

This commit is contained in:
jingrow 2026-06-07 18:48:07 +08:00
parent bd0ce165c6
commit 0d57fa7576
2 changed files with 105 additions and 30 deletions

View File

@ -53,8 +53,12 @@ export function useTableHandle(editorRef: Ref<Editor | null>) {
// ── Helpers ──────────────────────────────────────────────────────────────────
/** 获取当前 table DOM 元素 */
/** 获取当前 table DOM 元素(含过期检测) */
function getTableEl(): HTMLTableElement | null {
// 检查缓存是否已脱离 DOMProseMirror 重渲染后可能过期)
if (currentTableEl && !document.contains(currentTableEl)) {
currentTableEl = null
}
return currentTableEl ?? (editorRef.value?.view?.dom?.querySelector('table') ?? null)
}
@ -335,11 +339,58 @@ export function useTableHandle(editorRef: Ref<Editor | null>) {
function addColLeft() { execCmd('addColumnBefore') }
function addColRight() { execCmd('addColumnAfter') }
// ── DOM 索引 → 文档位置计算 ──────────────────────────────────────────────
/**
*
* findTable / posAtCoords
*/
function findTablePos(doc: any): number | null {
function recurse(node: any, pos: number): number | null {
if (node.type.name === 'table') return pos
let childPos = pos + (pos ? 1 : 0) // 文档根 pos=0 子节点从 0 开始,其他从 pos+1 开始
for (let i = 0; i < node.childCount; i++) {
const result = recurse(node.child(i), childPos)
if (result !== null) return result
childPos += node.child(i).nodeSize
}
return null
}
return recurse(doc, 0)
}
/**
* /
* view.posAtCoords()
* view.nodeDOM()TableView NodeView null
*/
function findCellPosByRowCol(doc: any, tablePos: number, rowIndex: number, colIndex: number): any | null {
const tableNode = doc.nodeAt(tablePos)
if (!tableNode || tableNode.type.name !== 'table') return null
if (rowIndex >= tableNode.childCount) return null
let pos = tablePos + 1 // 跳过 table 开标签
for (let r = 0; r < rowIndex; r++) {
pos += tableNode.child(r).nodeSize
}
const row = tableNode.child(rowIndex)
if (colIndex >= row.childCount) return null
pos += 1 // 跳过 row 开标签
for (let c = 0; c < colIndex; c++) {
pos += row.child(c).nodeSize
}
// pos 指向目标 cell 的开标签位置
// doc.resolve(pos) 向内解析到单元格内容起始处,供 CellSelection 正确定位
return doc.resolve(pos)
}
/**
*
* posAtCoords CellSelection
* editor.chain() .focus() CellSelection TextSelection
*
*
* DOM /
* posAtCoords nodeDOMTableView null
*
* @returns
*/
function execTableCmdAtCell(name: string, targetCellSelector: string): boolean {
@ -352,12 +403,23 @@ export function useTableHandle(editorRef: Ref<Editor | null>) {
const cell = table.querySelector(targetCellSelector) as HTMLElement | null
if (!cell) return false
const rect = cell.getBoundingClientRect()
const coords = view.posAtCoords({ left: rect.left + 2, top: rect.top + 2 })
if (!coords) return false
// 从 DOM 获取行/列索引,计算文档位置
let $cell: ReturnType<typeof cellAround> = null
const tr = cell.closest('tr')
if (tr) {
const tbody = tr.parentElement
if (tbody) {
const rowIndex = Array.from(tbody.children).indexOf(tr)
const colIndex = Array.from(tr.children).indexOf(cell)
if (rowIndex >= 0 && colIndex >= 0) {
const tablePos = findTablePos(view.state.doc)
if (tablePos !== null) {
$cell = findCellPosByRowCol(view.state.doc, tablePos, rowIndex, colIndex)
}
}
}
}
const $pos = view.state.doc.resolve(coords.pos)
const $cell = cellAround($pos)
if (!$cell) return false
// 先 focus view再设置 CellSelection

View File

@ -181,17 +181,22 @@ const SCROLL_SPEED = 8
/** 向上遍历 DOM 找真正的滚动容器Naive UI n-scrollbar 等) */
function findScrollContainer(el: HTMLElement): HTMLElement | Window {
let cur: HTMLElement | null = el.parentElement
let first: HTMLElement | null = null
let firstV: HTMLElement | null = null
let firstH: HTMLElement | null = null
while (cur) {
if (cur === document.body) break
const s = getComputedStyle(cur)
if (/auto|scroll/.test(s.overflowY + s.overflow)) {
if (!first) first = cur
if (cur.scrollHeight > cur.clientHeight) return cur
if (/auto|scroll/.test(s.overflowY + s.overflow) && cur.scrollHeight > cur.clientHeight) {
if (!firstV) firstV = cur
}
if (/auto|scroll/.test(s.overflowX + s.overflow) && cur.scrollWidth > cur.clientWidth) {
if (!firstH) firstH = cur
}
cur = cur.parentElement
}
return first || window
// 使使
const preferred = _dragDirection.value === 'col' ? (firstH || firstV) : (firstV || firstH)
return preferred || window
}
/** 缓存的滚动容器引用(初始化后不变) */
@ -206,17 +211,24 @@ function autoScrollDuringDrag(e: MouseEvent) {
const sc = _scrollContainer
const distBottom = window.innerHeight - e.clientY
const distRight = window.innerWidth - e.clientX
if (_dragDirection.value === 'row' && distBottom < SCROLL_EDGE) {
if (sc === window) {
window.scrollBy(0, SCROLL_SPEED)
} else {
;(sc as HTMLElement).scrollTop += SCROLL_SPEED
const distLeft = e.clientX
const distTop = e.clientY
if (_dragDirection.value === 'row') {
if (distBottom < SCROLL_EDGE) {
if (sc === window) window.scrollBy(0, SCROLL_SPEED)
else (sc as HTMLElement).scrollTop += SCROLL_SPEED
} else if (distTop < SCROLL_EDGE) {
if (sc === window) window.scrollBy(0, -SCROLL_SPEED)
else (sc as HTMLElement).scrollTop -= SCROLL_SPEED
}
} else if (_dragDirection.value === 'col' && distRight < SCROLL_EDGE) {
if (sc === window) {
window.scrollBy(SCROLL_SPEED, 0)
} else {
;(sc as HTMLElement).scrollLeft += SCROLL_SPEED
} else if (_dragDirection.value === 'col') {
if (distRight < SCROLL_EDGE) {
if (sc === window) window.scrollBy(SCROLL_SPEED, 0)
else (sc as HTMLElement).scrollLeft += SCROLL_SPEED
} else if (distLeft < SCROLL_EDGE) {
if (sc === window) window.scrollBy(-SCROLL_SPEED, 0)
else (sc as HTMLElement).scrollLeft -= SCROLL_SPEED
}
}
}
@ -224,7 +236,7 @@ function autoScrollDuringDrag(e: MouseEvent) {
function onAddRowStripMouseDown(e: MouseEvent) {
e.preventDefault()
_netRowCount.value = 0
_dragStartY.value = e.clientY
_dragStartY.value = e.pageY
_isDragging.value = false
_dragDirection.value = 'row'
;(e.currentTarget as HTMLElement).classList.add('is-dragging')
@ -236,7 +248,7 @@ function onAddRowStripMouseDown(e: MouseEvent) {
function onAddColStripMouseDown(e: MouseEvent) {
e.preventDefault()
_netColCount.value = 0
_dragStartX.value = e.clientX
_dragStartX.value = e.pageX
_isDragging.value = false
_dragDirection.value = 'col'
;(e.currentTarget as HTMLElement).classList.add('is-dragging')
@ -251,9 +263,9 @@ const DRAG_THRESHOLD = 5
const MAX_DRAG_ITERATIONS = 50
function onDragAddRow(e: MouseEvent) {
if (!_isDragging.value && Math.abs(e.clientY - _dragStartY.value) < DRAG_THRESHOLD) return
if (!_isDragging.value && Math.abs(e.pageY - _dragStartY.value) < DRAG_THRESHOLD) return
_isDragging.value = true
const deltaY = e.clientY - _dragStartY.value
const deltaY = e.pageY - _dragStartY.value
const targetCount = deltaY >= 0 ? Math.floor(deltaY / 28) : -Math.floor(-deltaY / 28)
//
@ -278,9 +290,9 @@ function onDragAddRow(e: MouseEvent) {
}
function onDragAddCol(e: MouseEvent) {
if (!_isDragging.value && Math.abs(e.clientX - _dragStartX.value) < DRAG_THRESHOLD) return
if (!_isDragging.value && Math.abs(e.pageX - _dragStartX.value) < DRAG_THRESHOLD) return
_isDragging.value = true
const deltaX = e.clientX - _dragStartX.value
const deltaX = e.pageX - _dragStartX.value
const targetCount = deltaX >= 0 ? Math.floor(deltaX / 28) : -Math.floor(-deltaX / 28)
//
@ -327,6 +339,7 @@ function onAddColEnd() {
function cleanupAddDrag() {
_isDragging.value = false
_dragDirection.value = null
_scrollContainer = undefined //
document.querySelectorAll('.be-table-add-strip.is-dragging').forEach(el => el.classList.remove('is-dragging'))
document.body.classList.remove('be-table-dragging-row', 'be-table-dragging-col')
}