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 ────────────────────────────────────────────────────────────────── // ── Helpers ──────────────────────────────────────────────────────────────────
/** 获取当前 table DOM 元素 */ /** 获取当前 table DOM 元素(含过期检测) */
function getTableEl(): HTMLTableElement | null { function getTableEl(): HTMLTableElement | null {
// 检查缓存是否已脱离 DOMProseMirror 重渲染后可能过期)
if (currentTableEl && !document.contains(currentTableEl)) {
currentTableEl = null
}
return currentTableEl ?? (editorRef.value?.view?.dom?.querySelector('table') ?? 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 addColLeft() { execCmd('addColumnBefore') }
function addColRight() { execCmd('addColumnAfter') } 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 * @returns
*/ */
function execTableCmdAtCell(name: string, targetCellSelector: string): boolean { 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 const cell = table.querySelector(targetCellSelector) as HTMLElement | null
if (!cell) return false if (!cell) return false
const rect = cell.getBoundingClientRect() // 从 DOM 获取行/列索引,计算文档位置
const coords = view.posAtCoords({ left: rect.left + 2, top: rect.top + 2 }) let $cell: ReturnType<typeof cellAround> = null
if (!coords) return false 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 if (!$cell) return false
// 先 focus view再设置 CellSelection // 先 focus view再设置 CellSelection

View File

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