fix: Block Editor表格块向右拖拽溢出后往回拖拽无法删除列
This commit is contained in:
parent
bd0ce165c6
commit
0d57fa7576
@ -53,8 +53,12 @@ export function useTableHandle(editorRef: Ref<Editor | null>) {
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/** 获取当前 table DOM 元素 */
|
||||
/** 获取当前 table DOM 元素(含过期检测) */
|
||||
function getTableEl(): HTMLTableElement | null {
|
||||
// 检查缓存是否已脱离 DOM(ProseMirror 重渲染后可能过期)
|
||||
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(动态增列后失准)或 nodeDOM(TableView 内返回 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
|
||||
|
||||
@ -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')
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user